Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> {0}. @implicitCast may only be used in " +
"externs.");
static final DiagnosticType INCOMPATIBLE_EXTENDED_PROPERTY_TYPE =
DiagnosticType.warning(
"JSC_INCOMPATIBLE_EXTENDED_PROPERTY_TYPE",
"Interface {0} has a property {1} with incompatible types in " +
"its super interfaces {2} and {3}");
static final DiagnosticType EXPECTED_THIS_TYPE =
DiagnosticType.warning(
"JSC_EXPECTED_THIS_TYPE",
"\"{0}\" must be called with a \"this\" type");
static final DiagnosticGroup ALL_DIAGNOSTICS = new DiagnosticGroup(
DETERMINISTIC_TEST,
DETERMINISTIC_TEST_NO_RESULT,
INEXISTENT_ENUM_ELEMENT,
INEXISTENT_PROPERTY,
NOT_A_CONSTRUCTOR,
BIT_OPERATION,
NOT_CALLABLE,
CONSTRUCTOR_NOT_CALLABLE,
FUNCTION_MASKS_VARIABLE,
MULTIPLE_VAR_DEF,
ENUM_DUP,
ENUM_NOT_CONSTANT,
INVALID_INTERFACE_MEMBER_DECLARATION,
INTERFACE_FUNCTION_NOT_EMPTY,
CONFLICTING_EXTENDED_TYPE,
CONFLICTING_IMPLEMENTED_TYPE,
BAD_IMPLEMENTED_TYPE,
HIDDEN_SUPERCLASS_PROPERTY,
HIDDEN_INTERFACE_PROPERTY,
HIDDEN_SUPERCLASS_PROPERTY_MISMATCH,
UNKNOWN_OVERRIDE,
INTERFACE_METHOD_OVERRIDE,
UNKNOWN_EXPR_TYPE,
UNRESOLVED_TYPE,
WRONG_ARGUMENT_COUNT,
ILLEGAL_IMPLICIT_CAST,
INCOMPATIBLE_EXTENDED_PROPERTY_TYPE,
EXPECTED_THIS_TYPE,
RhinoErrorReporter.TYPE_PARSE_ERROR,
TypedScopeCreator.UNKNOWN_LENDS,
TypedScopeCreator.LENDS_ON_NON_OBJECT,
TypedScopeCreator.CTOR_INITIALIZER,
TypedScopeCreator.IFACE_INITIALIZER,
FunctionTypeBuilder.THIS_TYPE_NON_OBJECT);
private final AbstractCompiler compiler;
private final TypeValidator validator;
private final ReverseAbstractInterpreter reverseInterpreter;
private final JSTypeRegistry typeRegistry;
private Scope topScope;
private ScopeCreator scopeCreator;
private final CheckLevel reportMissingOverride;
private final CheckLevel reportUnknownTypes;
// This may be expensive, so don't emit these warnings if they're
// explicitly turned off.
private boolean reportMissingProperties = true;
private InferJSDocInfo inferJSDocInfo = null;
// These fields are used to calculate the percentage of expressions typed.
private int typedCount = 0;
private int nullCount = 0;
private int unknownCount = 0;
private boolean inExterns;
// A state boolean to see we are currently in @notypecheck section of the
// code.
private int noTypeCheckSection = 0;
public TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry,
Scope topScope,
ScopeCreator scopeCreator,
CheckLevel reportMissingOverride,
CheckLevel reportUnknownTypes) {
this.compiler = compiler;
this.validator
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> = compiler.getTypeValidator();
this.reverseInterpreter = reverseInterpreter;
this.typeRegistry = typeRegistry;
this.topScope = topScope;
this.scopeCreator = scopeCreator;
this.reportMissingOverride = reportMissingOverride;
this.reportUnknownTypes = reportUnknownTypes;
this.inferJSDocInfo = new InferJSDocInfo(compiler);
}
public TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry,
CheckLevel reportMissingOverride,
CheckLevel reportUnknownTypes) {
this(compiler, reverseInterpreter, typeRegistry, null, null,
reportMissingOverride, reportUnknownTypes);
}
TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry) {
this(compiler, reverseInterpreter, typeRegistry, null, null,
CheckLevel.WARNING, CheckLevel.OFF);
}
/** Turn on the missing property check. Returns this for easy chaining. */
TypeCheck reportMissingProperties(boolean report) {
reportMissingProperties = report;
return this;
}
/**
* Main entry point for this phase of processing. This follows the pattern for
* JSCompiler phases.
*
* @param externsRoot The root of the externs parse tree.
* @param jsRoot The root of the input parse tree to be checked.
*/
public void process(Node externsRoot, Node jsRoot) {
Preconditions.checkNotNull(scopeCreator);
Preconditions.checkNotNull(topScope);
Node externsAndJs = jsRoot.getParent();
Preconditions.checkState(externsAndJs != null);
Preconditions.checkState(
externsRoot == null || externsAndJs.hasChild(externsRoot));
if (externsRoot != null) {
check(externsRoot, true);
}
check(jsRoot, false);
}
/** Main entry point of this phase for testing code. */
public Scope processForTesting(Node externsRoot, Node jsRoot) {
Preconditions.checkState(scopeCreator == null);
Preconditions.checkState(topScope == null);
Preconditions.checkState(jsRoot.getParent() != null);
Node externsAndJsRoot = jsRoot.getParent();
scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = scopeCreator.createScope(externsAndJsRoot, null);
TypeInferencePass inference = new TypeInferencePass(compiler,
reverseInterpreter, topScope, scopeCreator);
inference.process(externsRoot, jsRoot);
process(externsRoot, jsRoot);
return topScope;
}
public void check(Node node, boolean externs) {
Preconditions.checkNotNull(node);
NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator);
inExterns = externs;
t.traverseWithScope(node, topScope);
if (externs) {
inferJSDocInfo.process(node, null);
} else {
inferJSDocInfo.process(null, node);
}
}
private void checkNoTypeCheckSection(Node n, boolean enterSection) {
switch (n.getType()) {
case Token.SCRIPT:
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
case Token.BLOCK:
case Token.VAR:
case Token.FUNCTION:
case Token.ASSIGN:
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.isNoTypeCheck()) {
if (enterSection) {
noTypeCheckSection++;
} else {
noTypeCheckSection--;
}
}
validator.setShouldReport(noTypeCheckSection == 0);
break;
}
}
private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType,
String... arguments) {
if (noTypeCheckSection == 0) {
t.report(n, diagnosticType, arguments);
}
}
public boolean shouldTraverse(
NodeTraversal t, Node n, Node parent) {
checkNoTypeCheckSection(n, true);
switch (n.getType()) {
case Token.FUNCTION:
// normal type checking
final TypeCheck outerThis = this;
final Scope outerScope = t.getScope();
final FunctionType functionType = (FunctionType) n.getJSType();
final String functionPrivateName = n.getFirstChild().getString();
if (functionPrivateName != null && functionPrivateName.length() > 0 &&
outerScope.isDeclared(functionPrivateName, false) &&
// Ideally, we would want to check whether the type in the scope
// differs from the type being defined, but then the extern
// redeclarations of built-in types generates spurious warnings.
!(outerScope.getVar(
functionPrivateName).getType() instanceof FunctionType)) {
report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName);
}
// TODO(user): Only traverse the function's body. The function's
// name and arguments are traversed by the scope creator, and ideally
// should not be traversed by the type checker.
break;
}
return true;
}
/**
* This is the meat of the type checking. It is basically one big switch,
* with each case representing one type of parse tree node. The individual
* cases are usually pretty straightforward.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
*/
public void visit(NodeTraversal t, Node n, Node parent) {
JSType childType;
JSType leftType, rightType;
Node left, right;
// To be explicitly set to false if the node is not typeable.
boolean typeable = true;
switch (n.getType()) {
case Token.NAME:
typeable = visitName(t, n, parent);
break;
case Token.LP:
// If this is under a FUNCTION node, it is a parameter list and can be
// ignored here.
if (parent.getType() != Token.FUNCTION) {
ensureTyped(t, n, getJSType(n.getFirstChild()));
} else {
typeable = false;
}
break;
case Token.COMMA:
ensureTyped(t, n, get
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>JSType(n.getLastChild()));
break;
case Token.TRUE:
case Token.FALSE:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.THIS:
ensureTyped(t, n, t.getScope().getTypeOfThis());
break;
case Token.REF_SPECIAL:
ensureTyped(t, n);
break;
case Token.GET_REF:
ensureTyped(t, n, getJSType(n.getFirstChild()));
break;
case Token.NULL:
ensureTyped(t, n, NULL_TYPE);
break;
case Token.NUMBER:
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.STRING:
// Object literal keys are handled with OBJECTLIT
if (!NodeUtil.isObjectLitKey(n, n.getParent())) {
ensureTyped(t, n, STRING_TYPE);
}
break;
case Token.GET:
case Token.SET:
// Object literal keys are handled with OBJECTLIT
break;
case Token.ARRAYLIT:
ensureTyped(t, n, ARRAY_TYPE);
break;
case Token.REGEXP:
ensureTyped(t, n, REGEXP_TYPE);
break;
case Token.GETPROP:
visitGetProp(t, n, parent);
typeable = !(parent.getType() == Token.ASSIGN &&
parent.getFirstChild() == n);
break;
case Token.GETELEM:
visitGetElem(t, n);
// The type of GETELEM is always unknown, so no point counting that.
// If that unknown leaks elsewhere (say by an assignment to another
// variable), then it will be counted.
typeable = false;
break;
case Token.VAR:
visitVar(t, n);
typeable = false;
break;
case Token.NEW:
visitNew(t, n);
typeable = true;
break;
case Token.CALL:
visitCall(t, n);
typeable = !NodeUtil.isExpressionNode(parent);
break;
case Token.RETURN:
visitReturn(t, n);
typeable = false;
break;
case Token.DEC:
case Token.INC:
left = n.getFirstChild();
validator.expectNumber(
t, left, getJSType(left), "increment/decrement");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.NOT:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.VOID:
ensureTyped(t, n, VOID_TYPE);
break;
case Token.TYPEOF:
ensureTyped(t, n, STRING_TYPE);
break;
case Token.BITNOT:
childType = getJSType(n.getFirstChild());
if (!childType.matchesInt32Context()) {
report(t, n, BIT_OPERATION, NodeUtil.opToStr(n.getType()),
childType.toString());
}
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.POS:
case Token.NEG:
left = n.getFirstChild();
validator.expectNumber
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>(t, left, getJSType(left), "sign operator");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.EQ:
case Token.NE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
TernaryValue result =
leftTypeRestricted.testForEquality(rightTypeRestricted);
if (result != TernaryValue.UNKNOWN) {
if (n.getType() == Token.NE) {
result = result.not();
}
report(t, n, DETERMINISTIC_TEST, leftType.toString(),
rightType.toString(), result.toString());
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
}
case Token.SHEQ:
case Token.SHNE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
if (!leftTypeRestricted.canTestForShallowEqualityWith(
rightTypeRestricted)) {
report(t, n, DETERMINISTIC_TEST_NO_RESULT, leftType.toString(),
rightType.toString());
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
}
case Token.LT:
case Token.LE:
case Token.GT:
case Token.GE:
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
if (rightType.isNumber()) {
validator.expectNumber(
t, n, leftType, "left side of numeric comparison");
} else if (leftType.isNumber()) {
validator.expectNumber(
t, n, rightType, "right side of numeric comparison");
} else if (leftType.matchesNumberContext() &&
rightType.matchesNumberContext()) {
// OK.
} else {
// Whether the comparison is numeric will be determined at runtime
// each time the expression is evaluated. Regardless, both operands
// should match a string context.
String message = "left side of comparison";
validator.expectString(t, n, leftType, message);
validator.expectNotNullOrUndefined(
t, n, leftType, message, getNativeType(STRING_TYPE));
message = "right side of comparison";
validator.expectString(t, n, rightType, message);
validator.expectNotNullOrUndefined(
t, n, rightType, message, getNativeType(STRING_TYPE));
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.IN:
left = n.getFirstChild();
right = n.getLastChild();
leftType = getJSType(left);
rightType = getJSType(right);
validator.expectObject(t, n, rightType, "'in' requires an object");
validator.expectString(t
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>, left, leftType, "left side of 'in'");
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.INSTANCEOF:
left = n.getFirstChild();
right = n.getLastChild();
leftType = getJSType(left);
rightType = getJSType(right).restrictByNotNullOrUndefined();
validator.expectAnyObject(
t, left, leftType, "deterministic instanceof yields false");
validator.expectActualObject(
t, right, rightType, "instanceof requires an object");
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.ASSIGN:
visitAssign(t, n);
typeable = false;
break;
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_SUB:
case Token.ASSIGN_ADD:
case Token.ASSIGN_MUL:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.DIV:
case Token.MOD:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.SUB:
case Token.ADD:
case Token.MUL:
visitBinaryOperator(n.getType(), t, n);
break;
case Token.DELPROP:
if (!isReference(n.getFirstChild())) {
report(t, n, BAD_DELETE);
}
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.CASE:
JSType switchType = getJSType(parent.getFirstChild());
JSType caseType = getJSType(n.getFirstChild());
validator.expectSwitchMatchesCase(t, n, switchType, caseType);
typeable = false;
break;
case Token.WITH: {
Node child = n.getFirstChild();
childType = getJSType(child);
validator.expectObject(
t, child, childType, "with requires an object");
typeable = false;
break;
}
case Token.FUNCTION:
visitFunction(t, n);
break;
// These nodes have no interesting type behavior.
case Token.LABEL:
case Token.LABEL_NAME:
case Token.SWITCH:
case Token.BREAK:
case Token.CATCH:
case Token.TRY:
case Token.SCRIPT:
case Token.EXPR_RESULT:
case Token.BLOCK:
case Token.EMPTY:
case Token.DEFAULT:
case Token.CONTINUE:
case Token.DEBUGGER:
case Token.THROW:
typeable = false;
break;
// These nodes require data flow analysis.
case Token.DO:
case Token.FOR:
case Token.IF:
case Token.WHILE:
typeable = false;
break;
// These nodes are typed during the type inference.
case Token.AND:
case Token.HOOK:
case Token.OBJECTLIT:
case
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> Token.OR:
if (n.getJSType() != null) { // If we didn't run type inference.
ensureTyped(t, n);
} else {
// If this is an enum, then give that type to the objectlit as well.
if ((n.getType() == Token.OBJECTLIT)
&& (parent.getJSType() instanceof EnumType)) {
ensureTyped(t, n, parent.getJSType());
} else {
ensureTyped(t, n);
}
}
if (n.getType() == Token.OBJECTLIT) {
for (Node key : n.children()) {
visitObjLitKey(t, key, n);
}
}
break;
default:
report(t, n, UNEXPECTED_TOKEN, Token.name(n.getType()));
ensureTyped(t, n);
break;
}
// Don't count externs since the user's code may not even use that part.
typeable = typeable && !inExterns;
if (typeable) {
doPercentTypedAccounting(t, n);
}
checkNoTypeCheckSection(n, false);
}
/**
* Counts the given node in the typed statistics.
* @param n a node that should be typed
*/
private void doPercentTypedAccounting(NodeTraversal t, Node n) {
JSType type = n.getJSType();
if (type == null) {
nullCount++;
} else if (type.isUnknownType()) {
if (reportUnknownTypes.isOn()) {
compiler.report(
t.makeError(n, reportUnknownTypes, UNKNOWN_EXPR_TYPE));
}
unknownCount++;
} else {
typedCount++;
}
}
/**
* Visits an assignment <code>lvalue = rvalue</code>. If the
* <code>lvalue</code> is a prototype modification, we change the schema
* of the object type it is referring to.
* @param t the traversal
* @param assign the assign node
* (<code>assign.getType() == Token.ASSIGN</code> is an implicit invariant)
*/
private void visitAssign(NodeTraversal t, Node assign) {
JSDocInfo info = assign.getJSDocInfo();
Node lvalue = assign.getFirstChild();
Node rvalue = assign.getLastChild();
if (lvalue.getType() == Token.GETPROP) {
Node object = lvalue.getFirstChild();
JSType objectJsType = getJSType(object);
String property = lvalue.getLastChild().getString();
// the first name in this getprop refers to an interface
// we perform checks in addition to the ones below
if (object.getType() == Token.GETPROP) {
JSType jsType = getJSType(object.getFirstChild());
if (jsType.isInterface() &&
object.getLastChild().getString().equals("prototype")) {
visitInterfaceGetprop(t, assign, object, property, lvalue, rvalue);
}
}
// /** @type ... */object.name = ...;
if (info != null && info.hasType()) {
visitAnnotatedAssignGetprop
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>(t, assign,
info.getType().evaluate(t.getScope(), typeRegistry), object,
property, rvalue);
return;
}
// /** @enum ... */object.name = ...;
if (info != null && info.hasEnumParameterType()) {
checkEnumInitializer(
t, rvalue, info.getEnumParameterType().evaluate(
t.getScope(), typeRegistry));
return;
}
// object.prototype = ...;
if (property.equals("prototype")) {
if (objectJsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) objectJsType;
if (functionType.isConstructor()) {
JSType rvalueType = rvalue.getJSType();
validator.expectObject(t, rvalue, rvalueType,
OVERRIDING_PROTOTYPE_WITH_NON_OBJECT);
}
} else {
// TODO(user): might want to flag that
}
return;
}
// object.prototype.property = ...;
if (object.getType() == Token.GETPROP) {
Node object2 = object.getFirstChild();
String property2 = NodeUtil.getStringValue(object.getLastChild());
if ("prototype".equals(property2)) {
JSType jsType = object2.getJSType();
if (jsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) jsType;
if (functionType.isConstructor() || functionType.isInterface()) {
checkDeclaredPropertyInheritance(
t, assign, functionType, property, info, getJSType(rvalue));
}
} else {
// TODO(user): might want to flag that
}
return;
}
}
// object.property = ...;
ObjectType type = ObjectType.cast(
objectJsType.restrictByNotNullOrUndefined());
if (type != null) {
if (type.hasProperty(property) &&
!type.isPropertyTypeInferred(property) &&
!propertyIsImplicitCast(type, property)) {
validator.expectCanAssignToPropertyOf(
t, assign, getJSType(rvalue),
type.getPropertyType(property), object, property);
}
return;
}
} else if (lvalue.getType() == Token.NAME) {
// variable with inferred type case
JSType rvalueType = getJSType(assign.getLastChild());
Var var = t.getScope().getVar(lvalue.getString());
if (var != null) {
if (var.isTypeInferred()) {
return;
}
}
}
// fall through case
JSType leftType = getJSType(lvalue);
Node rightChild = assign.getLastChild();
JSType rightType = getJSType(rightChild);
if (validator.expectCanAssignTo(
t, assign, rightType, leftType, "assignment")) {
ensureTyped(t, assign, rightType);
} else {
ensureTyped(t, assign);
}
}
/**
* Visits an object literal field definition <code>key : value</code>.
*
* If the <code>lvalue</code> is a prototype modification, we change the
* schema of the
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> (!foundInterfaceProperty) {
// there is no superclass nor interface implementation
compiler.report(
t.makeError(n, UNKNOWN_OVERRIDE,
propertyName, ctorType.getInstanceType().toString()));
}
}
/**
* Given a constructor or an interface type, find out whether the unknown
* type is a supertype of the current type.
*/
private static boolean hasUnknownOrEmptySupertype(FunctionType ctor) {
Preconditions.checkArgument(ctor.isConstructor() || ctor.isInterface());
Preconditions.checkArgument(!ctor.isUnknownType());
// The type system should notice inheritance cycles on its own
// and break the cycle.
while (true) {
ObjectType maybeSuperInstanceType =
ctor.getPrototype().getImplicitPrototype();
if (maybeSuperInstanceType == null) {
return false;
}
if (maybeSuperInstanceType.isUnknownType() ||
maybeSuperInstanceType.isEmptyType()) {
return true;
}
ctor = maybeSuperInstanceType.getConstructor();
if (ctor == null) {
return false;
}
Preconditions.checkState(ctor.isConstructor() || ctor.isInterface());
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* interface.property2.property = ...;
* </pre>
*/
private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object,
String property, Node lvalue, Node rvalue) {
JSType rvalueType = getJSType(rvalue);
// Only 2 values are allowed for methods:
// goog.abstractMethod
// function () {};
// or for properties, no assignment such as:
// InterfaceFoo.prototype.foobar;
String abstractMethodName =
compiler.getCodingConvention().getAbstractMethodName();
if (!rvalueType.isOrdinaryFunction() &&
!(rvalue.isQualifiedName() &&
rvalue.getQualifiedName().equals(abstractMethodName))) {
// This is bad i18n style but we don't localize our compiler errors.
String abstractMethodMessage = (abstractMethodName != null)
? ", or " + abstractMethodName
: "";
compiler.report(
t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION,
abstractMethodMessage));
}
if (assign.getLastChild().getType() == Token.FUNCTION
&& !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) {
compiler.report(
t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY,
abstractMethodName));
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* object.property = ...;
* </pre>
* that have an {@code @type} annotation.
*/
private void visitAnnotatedAssignGetprop(NodeTraversal t,
Node assign, JSType type, Node object, String property, Node rvalue) {
// verifying that the rvalue has the correct type
validator.expectCanAssignToPropertyOf(t, assign, getJSType(rvalue), type,
object, property);
}
/**
* Visits a NAME node.
*
* @param t The node traversal object
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
* @return whether the node is typeable or not
*/
boolean visitName(NodeTraversal t, Node n, Node parent) {
// At this stage, we need to determine whether this is a leaf
// node in an expression (which therefore needs to have a type
// assigned for it) versus some other decorative node that we
// can safely ignore. Function names, arguments (children of LP nodes) and
// variable declarations are ignored.
// TODO(user): remove this short-circuiting in favor of a
// pre order traversal of the FUNCTION, CATCH, LP and VAR nodes.
int parentNodeType = parent.getType();
if (parentNodeType == Token.FUNCTION ||
parentNodeType == Token.CATCH ||
parentNodeType == Token.LP ||
parentNodeType == Token.VAR) {
return false;
}
JSType type = n.getJSType();
if (type == null) {
type = getNativeType(UNKNOWN_TYPE);
Var var = t.getScope().getVar(n.getString());
if (var != null) {
JSType varType = var.getType();
if (varType != null) {
type = varType;
}
}
}
ensureTyped(t, n, type);
return true;
}
/**
* Visits a GETPROP node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of <code>n</code>
*/
private void visitGetProp(NodeTraversal t, Node n, Node parent) {
// GETPROP nodes have an assigned type on their node by the scope creator
// if this is an enum declaration. The only namespaced enum declarations
// that we allow are of the form object.name = ...;
if (n.getJSType() != null && parent.getType() == Token.ASSIGN) {
return;
}
// obj.prop or obj.method()
// Lots of types can appear on the left, a call to a void function can
// never be on the left. getPropertyType will decide what is acceptable
// and what isn't.
Node property = n.getLastChild();
Node objNode = n.getFirstChild();
JSType childType = getJSType(objNode);
// TODO(user): remove in favor of flagging every property access on
// non-object.
if (!validator.expectNotNullOrUndefined(t, n, childType,
childType + " has no properties", getNativeType(OBJECT_TYPE))) {
ensureTyped(t, n);
return;
}
checkPropertyAccess(childType, property.getString(), t, n);
ensureTyped(t, n);
}
/**
* Make sure that the access of this property is ok.
*/
private void checkPropertyAccess(JSType childType, String propName,
NodeTraversal t,
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> Node n) {
ObjectType objectType = childType.dereference();
if (objectType != null) {
JSType propType = getJSType(n);
if ((!objectType.hasProperty(propName) ||
objectType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) &&
propType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) {
if (objectType instanceof EnumType) {
report(t, n, INEXISTENT_ENUM_ELEMENT, propName);
} else if (!objectType.isEmptyType() &&
reportMissingProperties && !isPropertyTest(n)) {
if (!typeRegistry.canPropertyBeDefined(objectType, propName)) {
report(t, n, INEXISTENT_PROPERTY, propName,
validator.getReadableJSTypeName(n.getFirstChild(), true));
}
}
}
} else {
// TODO(nicksantos): might want to flag the access on a non object when
// it's impossible to get a property from this type.
}
}
/**
* Determines whether this node is testing for the existence of a property.
* If true, we will not emit warnings about a missing property.
*
* @param getProp The GETPROP being tested.
*/
private boolean isPropertyTest(Node getProp) {
Node parent = getProp.getParent();
switch (parent.getType()) {
case Token.CALL:
return parent.getFirstChild() != getProp &&
compiler.getCodingConvention().isPropertyTestFunction(parent);
case Token.IF:
case Token.WHILE:
case Token.DO:
case Token.FOR:
return NodeUtil.getConditionExpression(parent) == getProp;
case Token.INSTANCEOF:
case Token.TYPEOF:
return true;
case Token.AND:
case Token.HOOK:
return parent.getFirstChild() == getProp;
case Token.NOT:
return parent.getParent().getType() == Token.OR &&
parent.getParent().getFirstChild() == parent;
}
return false;
}
/**
* Visits a GETELEM node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitGetElem(NodeTraversal t, Node n) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
validator.expectIndexMatch(t, n, getJSType(left), getJSType(right));
ensureTyped(t, n);
}
/**
* Visits a VAR node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitVar(NodeTraversal t, Node n) {
// TODO(nicksantos): Fix this so that the doc info always shows up
// on the NAME node. We probably want to wait for the parser
// merge to fix this.
JSDocInfo varInfo = n.
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>hasOneChild() ? n.getJSDocInfo() : null;
for (Node name : n.children()) {
Node value = name.getFirstChild();
// A null var would indicate a bug in the scope creation logic.
Var var = t.getScope().getVar(name.getString());
if (value != null) {
JSType valueType = getJSType(value);
JSType nameType = var.getType();
nameType = (nameType == null) ? getNativeType(UNKNOWN_TYPE) : nameType;
JSDocInfo info = name.getJSDocInfo();
if (info == null) {
info = varInfo;
}
if (info != null && info.hasEnumParameterType()) {
// var.getType() can never be null, this would indicate a bug in the
// scope creation logic.
checkEnumInitializer(
t, value,
info.getEnumParameterType().evaluate(t.getScope(), typeRegistry));
} else if (var.isTypeInferred()) {
ensureTyped(t, name, valueType);
} else {
validator.expectCanAssignTo(
t, value, valueType, nameType, "initializing variable");
}
}
}
}
/**
* Visits a NEW node.
*/
private void visitNew(NodeTraversal t, Node n) {
Node constructor = n.getFirstChild();
FunctionType type = getFunctionType(constructor);
if (type != null && type.isConstructor()) {
visitParameterList(t, n, type);
ensureTyped(t, n, type.getInstanceType());
} else {
// TODO(user): add support for namespaced objects.
if (constructor.getType() != Token.GETPROP) {
// TODO(user): make the constructor node have lineno/charno
// and use constructor for a more precise error indication.
// It seems that GETPROP nodes are missing this information.
Node line;
if (constructor.getLineno() < 0 || constructor.getCharno() < 0) {
line = n;
} else {
line = constructor;
}
report(t, line, NOT_A_CONSTRUCTOR);
}
ensureTyped(t, n);
}
}
/**
* Check whether there's any property conflict for for a particular super
* interface
* @param t The node traversal object that supplies context
* @param n The node being visited
* @param functionName The function name being checked
* @param properties The property names in the super interfaces that have
* been visited
* @param currentProperties The property names in the super interface
* that have been visited
* @param interfaceType The super interface that is being visited
*/
private void checkInterfaceConflictProperties(NodeTraversal t, Node n,
String functionName, HashMap<String, ObjectType> properties,
HashMap<String, ObjectType> currentProperties,
ObjectType interfaceType) {
Set<String> currentPropertyNames = interfaceType.getPropertyNames();
for (String name : currentPropertyNames) {
ObjectType oType = properties.get(name);
if (oType != null) {
if (!interfaceType.getPropertyType(name).isEquivalentTo(
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>, n, functionPrivateName,
properties, currentProperties, interfaceType);
properties.putAll(currentProperties);
}
}
}
}
/**
* Visits a CALL node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitCall(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
JSType childType = getJSType(child).restrictByNotNullOrUndefined();
if (!childType.canBeCalled()) {
report(t, n, NOT_CALLABLE, childType.toString());
ensureTyped(t, n);
return;
}
// A couple of types can be called as if they were functions.
// If it is a function type, then validate parameters.
if (childType instanceof FunctionType) {
FunctionType functionType = (FunctionType) childType;
boolean isExtern = false;
private void visitParameterList(NodeTraversal t, Node call,
FunctionType functionType) {
Iterator<Node> arguments = call.children().iterator();
arguments.next(); // skip the function name
Iterator<Node> parameters = functionType.getParameters().iterator();
int ordinal = 0;
Node parameter = null;
Node argument = null;
while (arguments.hasNext() &&
(parameters.hasNext() ||
parameter != null && parameter.isVarArgs())) {
// If there are no parameters left in the list, then the while loop
// above implies that this must be a var_args function.
if (parameters.hasNext()) {
parameter = parameters.next();
}
argument = arguments.next();
ordinal++;
validator.expectArgumentMatchesParameter(t, argument,
getJSType(argument), getJSType(parameter), call, ordinal);
}
int numArgs = call.getChildCount() - 1;
int minArgs = functionType.getMinArguments();
int maxArgs = functionType.getMaxArguments();
if (minArgs > numArgs || maxArgs < numArgs) {
report(t, call, WRONG_ARGUMENT_COUNT,
validator.getReadableJSTypeName(call.getFirstChild(), false),
String.valueOf(numArgs), String.valueOf(minArgs),
maxArgs != Integer.MAX_VALUE ?
" and no more than " + maxArgs + " argument(s)" : "");
}
}
/**
* Visits a RETURN node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitReturn(NodeTraversal t, Node n) {
Node function = t.getEnclosingFunction();
// This is a misplaced return, but the real JS will fail to compile,
// so let it go.
if (function == null) {
return;
}
JSType jsType = getJSType(function);
if (jsType instanceof FunctionType) {
FunctionType functionType = (FunctionType) jsType;
JSType returnType
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> = functionType.getReturnType();
// if no return type is specified, undefined must be returned
// (it's a void function)
if (returnType == null) {
returnType = getNativeType(VOID_TYPE);
}
// fetching the returned value's type
Node valueNode = n.getFirstChild();
JSType actualReturnType;
if (valueNode == null) {
actualReturnType = getNativeType(VOID_TYPE);
valueNode = n;
} else {
actualReturnType = getJSType(valueNode);
}
// verifying
validator.expectCanAssignTo(t, valueNode, actualReturnType, returnType,
"inconsistent return type");
}
}
/**
* This function unifies the type checking involved in the core binary
* operators and the corresponding assignment operators. The representation
* used internally is such that common code can handle both kinds of
* operators easily.
*
* @param op The operator.
* @param t The traversal object, needed to report errors.
* @param n The node being checked.
*/
private void visitBinaryOperator(int op, NodeTraversal t, Node n) {
Node left = n.getFirstChild();
JSType leftType = getJSType(left);
Node right = n.getLastChild();
JSType rightType = getJSType(right);
switch (op) {
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
case Token.URSH:
if (!leftType.matchesInt32Context()) {
report(t, left, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), leftType.toString());
}
if (!rightType.matchesUint32Context()) {
report(t, right, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), rightType.toString());
}
break;
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_SUB:
case Token.DIV:
case Token.MOD:
case Token.MUL:
case Token.SUB:
validator.expectNumber(t, left, leftType, "left operand");
validator.expectNumber(t, right, rightType, "right operand");
break;
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITOR:
case Token.BITAND:
case Token.BITXOR:
case Token.BITOR:
validator.expectBitwiseable(t, left, leftType,
"bad left operand to bitwise operator");
validator.expectBitwiseable(t, right, rightType,
"bad right operand to bitwise operator");
break;
case Token.ASSIGN_ADD:
case Token.ADD:
break;
default:
report(t, n, UNEXPECTED_TOKEN, Node.tokenToName(op));
}
ensureTyped(t, n);
}
/**
* <p>Checks the initializer of an enum. An enum can be initialized with an
* object literal
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> whose values must be subtypes of the declared enum element
* type, or by copying another enum.</p>
*
* <p>In the case of an enum copy, we verify that the enum element type of the
* enum used for initialization is a subtype of the enum element type of
* the enum the value is being copied in.</p>
*
* <p>Examples:</p>
* <pre>var myEnum = {FOO: ..., BAR: ...};
* var myEnum = myOtherEnum;</pre>
*
* @param value the value used for initialization of the enum
* @param primitiveType The type of each element of the enum.
*/
private void checkEnumInitializer(
NodeTraversal t, Node value, JSType primitiveType) {
if (value.getType() == Token.OBJECTLIT) {
for (Node key = value.getFirstChild();
key != null; key = key.getNext()) {
Node propValue = key.getFirstChild();
// the value's type must be assignable to the enum's primitive type
validator.expectCanAssignTo(
t, propValue, getJSType(propValue), primitiveType,
"element type must match enum's type");
}
} else if (value.getJSType() instanceof EnumType) {
// TODO(user): Remove the instanceof check in favor
// of a type.isEnumType() predicate. Currently, not all enum types are
// implemented by the EnumClass, e.g. the unknown type and the any
// type. The types need to be defined by interfaces such that an
// implementation can implement multiple types interface.
EnumType valueEnumType = (EnumType) value.getJSType();
JSType valueEnumPrimitiveType =
valueEnumType.getElementsType().getPrimitiveType();
validator.expectCanAssignTo(t, value, valueEnumPrimitiveType,
primitiveType, "incompatible enum element types");
} else {
// The error condition is handled in TypedScopeCreator.
}
}
/**
* This predicate is used to determine if the node represents an expression
* that is a Reference according to JavaScript definitions.
*
* @param n The node being checked.
* @return true if the sub-tree n is a reference, false otherwise.
*/
private static boolean isReference(Node n) {
switch (n.getType()) {
case Token.GETELEM:
case Token.GETPROP:
case Token.NAME:
return true;
default:
return false;
}
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(nicksantos): This branch indicates a compiler bug, not worthy of
// halting the compilation but we should log this and analyze to track
// down why it happens. This is not critical and will be resolved over
// time as the type checker is extended.
return getNativeType(UNKNOWN_TYPE);
} else {
return jsType;
}
}
/**
* Gets the type of the node or {@code
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> null} if the node's type is not a
* function.
*/
private FunctionType getFunctionType(Node n) {
JSType type = getJSType(n).restrictByNotNullOrUndefined();
if (type.isUnknownType()) {
return typeRegistry.getNativeFunctionType(U2U_CONSTRUCTOR_TYPE);
} else if (type instanceof FunctionType) {
return (FunctionType) type;
} else {
return null;
}
}
// TODO(nicksantos): TypeCheck should never be attaching types to nodes.
// All types should be attached by TypeInference. This is not true today
// for legacy reasons. There are a number of places where TypeInference
// doesn't attach a type, as a signal to TypeCheck that it needs to check
// that node's type.
/**
* Ensure that the given node has a type. If it does not have one,
* attach the UNKNOWN_TYPE.
*/
private void ensureTyped(NodeTraversal t, Node n) {
ensureTyped(t, n, getNativeType(UNKNOWN_TYPE));
}
private void ensureTyped(NodeTraversal t, Node n, JSTypeNative type) {
ensureTyped(t, n, getNativeType(type));
}
/**
* Enforces type casts, and ensures the node is typed.
*
* A cast in the way that we use it in JSDoc annotations never
* alters the generated code and therefore never can induce any runtime
* operation. What this means is that a 'cast' is really just a compile
* time constraint on the underlying value. In the future, we may add
* support for run-time casts for compiled tests.
*
* To ensure some shred of sanity, we enforce the notion that the
* type you are casting to may only meaningfully be a narrower type
* than the underlying declared type. We also invalidate optimizations
* on bad type casts.
*
* @param t The traversal object needed to report errors.
* @param n The node getting a type assigned to it.
* @param type The type to be assigned.
*/
private void ensureTyped(NodeTraversal t, Node n, JSType type) {
// Make sure FUNCTION nodes always get function type.
Preconditions.checkState(n.getType() != Token.FUNCTION ||
type instanceof FunctionType ||
type.isUnknownType());
JSDocInfo info = n.getJSDocInfo();
if (info != null) {
if (info.hasType()) {
JSType infoType = info.getType().evaluate(t.getScope(), typeRegistry);
validator.expectCanCast(t, n, infoType, type);
type = infoType;
}
if (info.isImplicitCast() && !inExterns) {
String propName = n.getType() == Token.GETPROP ?
n.getLastChild().getString() : "(missing)";
compiler.report(
t.makeError(n, ILLEGAL_IMPLICIT_CAST, propName));
}
}
if (n.getJSType() == null) {
n.setJSType(type);
}
}
/**
* Returns
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.*;
/**
* Verifies that constants are only assigned a value once.
* e.g. var XX = 5;
* XX = 3; // error!
* XX++; // error!
*
*/
class ConstCheck extends AbstractPostOrderCallback
implements CompilerPass {
static final DiagnosticType CONST_REASSIGNED_VALUE_ERROR =
DiagnosticType.error(
"JSC_CONSTANT_REASSIGNED_VALUE_ERROR",
"constant {0} assigned a value more than once");
private final AbstractCompiler compiler;
private final Set<Scope.Var> initializedConstants;
/**
* Creates an instance.
*/
public ConstCheck(AbstractCompiler compiler) {
this.compiler = compiler;
this.initializedConstants = new HashSet<Scope.Var>();
}
@Override
public void process(Node externs, Node root) {
Preconditions.checkState(compiler.getLifeCycleStage().isNormalized());
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
if (parent != null &&
parent.getType() == Token.VAR &&
n.hasChildren()) {
String name = n.getString();
Scope.Var var = t.getScope().getVar(name);
if (isConstant(var)) {
if (initializedConstants.contains(var)) {
reportError(t, n, name);
} else {
initializedConstants.add(var);
}
}
}
break;
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD: {
Node lhs = n.getFirstChild();
if (lhs.getType() == Token.NAME) {
String name = lhs.getString();
Scope.Var var
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> = t.getScope().getVar(name);
if (isConstant(var)) {
if (initializedConstants.contains(var)) {
reportError(t, n, name);
} else {
initializedConstants.add(var);
}
}
}
break;
}
case Token.INC:
case Token.DEC: {
Node lhs = n.getFirstChild();
if (lhs.getType() == Token.NAME) {
String name = lhs.getString();
Scope.Var var = t.getScope().getVar(name);
if (isConstant(var)) {
reportError(t, n, name);
}
}
break;
}
}
}
/**
* Gets whether a variable is a constant initialized to a literal value at
* the point where it is declared.
*/
private boolean isConstant(Scope.Var var) {
return var != null && var.isConst();
}
/**
* Reports a reassigned constant error.
*/
void reportError(NodeTraversal t, Node n, String name) {
compiler.report(t.makeError(n, CONST_REASSIGNED_VALUE_ERROR, name));
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>typeA.getTypesUnderShallowEquality(p.typeB);
}
};
/**
* Merging function for strict non-equality between types.
*/
private static final
Function<TypePair, TypePair> SHNE =
new Function<TypePair, TypePair>() {
public TypePair apply(TypePair p) {
if (p.typeA == null || p.typeB == null) {
return null;
}
return p.typeA.getTypesUnderShallowInequality(p.typeB);
}
};
/**
* Merging function for inequality comparisons between types.
*/
private final
Function<TypePair, TypePair> INEQ =
new Function<TypePair, TypePair>() {
public TypePair apply(TypePair p) {
return new TypePair(
getRestrictedWithoutUndefined(p.typeA),
getRestrictedWithoutUndefined(p.typeB));
}
};
/**
* Creates a semantic reverse abstract interpreter.
*/
SemanticReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
super(convention, typeRegistry);
}
public FlowScope getPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
// Check for the typeof operator.
int operatorToken = condition.getType();
switch (operatorToken) {
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.CASE:
Node left;
Node right;
if (operatorToken == Token.CASE) {
left = condition.getParent().getFirstChild(); // the switch condition
right = condition.getFirstChild();
} else {
left = condition.getFirstChild();
right = condition.getLastChild();
}
Node typeOfNode = null;
Node stringNode = null;
if (left.getType() == Token.TYPEOF && right.getType() == Token.STRING) {
typeOfNode = left;
stringNode = right;
} else if (right.getType() == Token.TYPEOF &&
left.getType() == Token.STRING) {
typeOfNode = right;
stringNode = left;
}
if (typeOfNode != null && stringNode != null) {
Node operandNode = typeOfNode.getFirstChild();
JSType operandType = getTypeIfRefinable(operandNode, blindScope);
if (operandType != null) {
boolean resultEqualsValue = operatorToken == Token.EQ ||
operatorToken == Token.SHEQ || operatorToken == Token.CASE;
if (!outcome) {
resultEqualsValue = !resultEqualsValue;
}
return caseTypeOf(operandNode, operandType, stringNode.getString(),
resultEqualsValue, blindScope);
}
}
}
switch (operatorToken) {
case Token.AND:
if (outcome) {
return caseAndOrNotShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, true);
}
case Token.OR:
if (!outcome) {
return caseAndOrNot
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>ShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
} else {
return caseAndOrMaybeShortCircuiting(condition.getFirstChild(),
condition.getLastChild(), blindScope, false);
}
case Token.EQ:
if (outcome) {
return caseEquality(condition, blindScope, EQ);
} else {
return caseEquality(condition, blindScope, NE);
}
case Token.NE:
if (outcome) {
return caseEquality(condition, blindScope, NE);
} else {
return caseEquality(condition, blindScope, EQ);
}
case Token.SHEQ:
if (outcome) {
return caseEquality(condition, blindScope, SHEQ);
} else {
return caseEquality(condition, blindScope, SHNE);
}
case Token.SHNE:
if (outcome) {
return caseEquality(condition, blindScope, SHNE);
} else {
return caseEquality(condition, blindScope, SHEQ);
}
case Token.NAME:
case Token.GETPROP:
return caseNameOrGetProp(condition, blindScope, outcome);
case Token.ASSIGN:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(),
firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild().getNext(), blindScope, outcome),
outcome);
case Token.NOT:
return firstPreciserScopeKnowingConditionOutcome(
condition.getFirstChild(), blindScope, !outcome);
case Token.LE:
case Token.LT:
case Token.GE:
case Token.GT:
if (outcome) {
return caseEquality(condition, blindScope, INEQ);
}
break;
case Token.INSTANCEOF:
return caseInstanceOf(
condition.getFirstChild(), condition.getLastChild(), blindScope,
outcome);
case Token.IN:
if (outcome && condition.getFirstChild().getType() == Token.STRING) {
return caseIn(condition.getLastChild(),
condition.getFirstChild().getString(), blindScope);
}
break;
case Token.CASE:
Node left =
condition.getParent().getFirstChild(); // the switch condition
Node right = condition.getFirstChild();
if (outcome) {
return caseEquality(left, right, blindScope, SHEQ);
} else {
return caseEquality(left, right, blindScope, SHNE);
}
}
return nextPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
private FlowScope caseEquality(Node condition, FlowScope blindScope,
Function<TypePair, TypePair> merging) {
return caseEquality(condition.getFirstChild(), condition.getLastChild(),
blindScope, merging);
}
private FlowScope caseEquality(Node left, Node right, FlowScope blindScope,
Function<TypePair, TypePair> merging) {
// left type
JSType leftType = getTypeIfRefinable(left, blindScope);
boolean leftIsRefineable;
if (leftType != null) {
leftIsRefineable = true;
} else {
leftIsRefineable = false;
leftType =
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> boolean condition) {
FlowScope leftScope = firstPreciserScopeKnowingConditionOutcome(
left, blindScope, !condition);
StaticSlot<JSType> leftVar = leftScope.findUniqueRefinedSlot(blindScope);
if (leftVar == null) {
return blindScope;
}
FlowScope rightScope = firstPreciserScopeKnowingConditionOutcome(
left, blindScope, condition);
rightScope = firstPreciserScopeKnowingConditionOutcome(
right, rightScope, !condition);
StaticSlot<JSType> rightVar = rightScope.findUniqueRefinedSlot(blindScope);
if (rightVar == null || !leftVar.getName().equals(rightVar.getName())) {
return blindScope;
}
JSType type = leftVar.getType().getLeastSupertype(rightVar.getType());
FlowScope informed = blindScope.createChildFlowScope();
informed.inferSlotType(leftVar.getName(), type);
return informed;
}
private FlowScope caseNameOrGetProp(Node name, FlowScope blindScope,
boolean outcome) {
JSType type = getTypeIfRefinable(name, blindScope);
if (type != null) {
JSType restrictedType =
type.getRestrictedTypeGivenToBooleanOutcome(outcome);
FlowScope informed = blindScope.createChildFlowScope();
declareNameInScope(informed, name, restrictedType);
return informed;
}
return blindScope;
}
private FlowScope caseTypeOf(Node node, JSType type, String value,
boolean resultEqualsValue, FlowScope blindScope) {
JSType restrictedType =
getRestrictedByTypeOfResult(type, value, resultEqualsValue);
if (restrictedType == null) {
return blindScope;
}
FlowScope informed = blindScope.createChildFlowScope();
declareNameInScope(informed, node, restrictedType);
return informed;
}
private FlowScope caseInstanceOf(Node left, Node right, FlowScope blindScope,
boolean outcome) {
JSType leftType = getTypeIfRefinable(left, blindScope);
if (leftType == null) {
return blindScope;
}
JSType rightType = right.getJSType();
ObjectType targetType =
typeRegistry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE);
if (rightType instanceof FunctionType) {
targetType = (FunctionType) rightType;
}
Visitor<JSType> visitor;
if (outcome) {
visitor = new RestrictByTrueInstanceOfResultVisitor(targetType);
} else {
visitor = new RestrictByFalseInstanceOfResultVisitor(targetType);
}
JSType restrictedLeftType = leftType.visit(visitor);
if (restrictedLeftType != null && !restrictedLeftType.equals(leftType)) {
FlowScope informed = blindScope.createChildFlowScope();
declareNameInScope(informed, left, restrictedLeftType);
return informed;
}
return blindScope;
}
/**
* Given 'property in object', ensures that the object has the property in the
* informed scope by defining it as a qualified name if the object type lacks
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Checks for non side effecting statements such as
* <pre>
* var s = "this string is "
* "continued on the next line but you forgot the +";
* x == foo(); // should that be '='?
* foo();; // probably just a stray-semicolon. Doesn't hurt to check though
* </p>
* and generates warnings.
*
*/
final class CheckSideEffects extends AbstractPostOrderCallback {
static final DiagnosticType USELESS_CODE_ERROR = DiagnosticType.warning(
"JSC_USELESS_CODE",
"Suspicious code. {0}");
private final CheckLevel level;
CheckSideEffects(CheckLevel level) {
this.level = level;
}
public void visit(NodeTraversal t, Node n, Node parent) {
// VOID nodes appear when there are extra semicolons at the BLOCK level.
// I've been unable to think of any cases where this indicates a bug,
// and apparently some people like keeping these semicolons around,
// so we'll allow it.
if (n.getType() == Token.EMPTY ||
n.getType() == Token.COMMA) {
return;
}
if (parent == null)
return;
int pt = parent.getType();
if (pt == Token.COMMA) {
Node gramps = parent.getParent();
if (gramps.getType() == Token.CALL &&
parent == gramps.getFirstChild()) {
// Semantically, a direct call to eval is different from an indirect
// call to an eval. See Ecma-262 S15.1.2.1. So it's ok for the first
// expression to a comma to be a no-op if it's used to indirect
// an eval.
if (n == parent.getFirstChild() &&
parent.getChildCount() == 2 &&
n.getNext().getType() == Token.NAME &&
"eval".equals(n.getNext().getString())) {
return;
}
}
if (n == parent.getLastChild()) {
for (Node an : parent.getAncestors()) {
int ancestorType = an.getType();
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> if (ancestorType == Token.COMMA)
continue;
if (ancestorType != Token.EXPR_RESULT &&
ancestorType != Token.BLOCK)
return;
else
break;
}
}
} else if (pt != Token.EXPR_RESULT && pt != Token.BLOCK) {
if (pt == Token.FOR && parent.getChildCount() == 4 &&
(n == parent.getFirstChild() ||
n == parent.getFirstChild().getNext().getNext())) {
// Fall through and look for warnings for the 1st and 3rd child
// of a for.
} else {
return; // it might be ok to not have a side-effect
}
}
if (NodeUtil.isSimpleOperatorType(n.getType()) ||
!NodeUtil.mayHaveSideEffects(n, t.getCompiler())) {
if (n.isQualifiedName() && n.getJSDocInfo() != null) {
// This no-op statement was there so that JSDoc information could
// be attached to the name. This check should not complain about it.
return;
} else if (NodeUtil.isExpressionNode(n)) {
// we already reported the problem when we visited the child.
return;
}
String msg = "This code lacks side-effects. Is there a bug?";
if (n.getType() == Token.STRING) {
msg = "Is there a missing '+' on the previous line?";
}
t.getCompiler().report(
t.makeError(n, level, USELESS_CODE_ERROR, msg));
}
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSTypeExpression;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Prepare the AST before we do any checks or optimizations on it.
*
* This pass must run. It should bring the AST into a consistent state,
* and add annotations where necessary. It should not make any transformations
* on the tree that would lose source information, since we need that source
* information for checks.
*
* @author johnlenz@google.com (John Lenz)
*/
class PrepareAst implements CompilerPass {
private final AbstractCompiler compiler;
private final boolean checkOnly;
PrepareAst(AbstractCompiler compiler) {
this(compiler, false);
}
PrepareAst(AbstractCompiler compiler, boolean checkOnly) {
this.compiler = compiler;
this.checkOnly = checkOnly;
}
private void reportChange() {
if (checkOnly) {
Preconditions.checkState(false, "normalizeNodeType constraints violated");
}
}
@Override
public void process(Node externs, Node root) {
if (checkOnly) {
normalizeNodeTypes(root);
} else {
// Don't perform "PrepareAnnotations" when doing checks as
// they currently aren't valid during sanity checks. In particular,
// they DIRECT_EVAL shouldn't be applied after inlining has been
// performed.
if (externs != null) {
NodeTraversal.traverse(
compiler, externs, new PrepareAnnotations(compiler));
}
if (root != null) {
NodeTraversal.traverse(
compiler, root, new PrepareAnnotations(compiler));
}
}
}
/**
* Covert EXPR_VOID to EXPR_RESULT to simplify the rest of the code.
*/
private void normalizeNodeTypes(Node n) {
if (n.getType() == Token.EXPR_VOID) {
n.setType(Token.EXPR_RESULT);
reportChange();
}
// Remove unused properties to minimize differences between ASTs
// produced by the two parsers.
if (n.getType() == Token.FUNCTION) {
Preconditions.checkState(n.getProp(Node.FUNCTION_PROP) == null);
}
normalizeBlocks(n);
for (Node child = n.getFirstChild();
child !=
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> null; child = child.getNext()) {
// This pass is run during the CompilerTestCase validation, so this
// parent pointer check serves as a more general check.
Preconditions.checkState(child.getParent() == n);
normalizeNodeTypes(child);
}
}
/**
* Add blocks to IF, WHILE, DO, etc.
*/
private void normalizeBlocks(Node n) {
if (NodeUtil.isControlStructure(n)
&& n.getType() != Token.LABEL
&& n.getType() != Token.SWITCH) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (NodeUtil.isControlStructureCodeBlock(n,c) &&
c.getType() != Token.BLOCK) {
Node newBlock = new Node(Token.BLOCK, n.getLineno(), n.getCharno());
newBlock.copyInformationFrom(n);
n.replaceChild(c, newBlock);
if (c.getType() != Token.EMPTY) {
newBlock.addChildrenToFront(c);
} else {
newBlock.setWasEmptyNode(true);
}
c = newBlock;
reportChange();
}
}
}
}
/**
* Normalize where annotations appear on the AST. Copies
* around existing JSDoc annotations as well as internal annotations.
*/
static class PrepareAnnotations
implements NodeTraversal.Callback {
private final CodingConvention convention;
PrepareAnnotations(AbstractCompiler compiler) {
this.convention = compiler.getCodingConvention();
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.OBJECTLIT) {
normalizeObjectLiteralAnnotations(n);
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
annotateCalls(n);
break;
case Token.FUNCTION:
annotateFunctions(n, parent);
annotateDispatchers(n, parent);
break;
}
}
private void normalizeObjectLiteralAnnotations(Node objlit) {
Preconditions.checkState(objlit.getType() == Token.OBJECTLIT);
for (Node key = objlit.getFirstChild();
key != null; key = key.getNext()) {
Node value = key.getFirstChild();
normalizeObjectLiteralKeyAnnotations(objlit, key, value);
}
}
/**
* There are two types of calls we are interested in calls without explicit
* "this" values (what we are call "free" calls) and direct call to eval.
*/
private void annotateCalls(Node n) {
Preconditions.checkState(n.getType() == Token.CALL);
// Keep track of of the "this" context of a call. A call without an
// explicit "this" is a free call.
Node first = n.getFirstChild();
if (!NodeUtil.isGet(first)) {
n.putBooleanProp(Node.FREE_CALL, true);
}
// Keep track of the context in which eval is called. It is important
// to distinguish between "(
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>0, eval)()" and "eval()".
if (first.getType() == Token.NAME &&
"eval".equals(first.getString())) {
first.putBooleanProp(Node.DIRECT_EVAL, true);
}
}
/**
* Translate dispatcher info into the property expected node.
*/
private void annotateDispatchers(Node n, Node parent) {
Preconditions.checkState(n.getType() == Token.FUNCTION);
if (parent.getJSDocInfo() != null
&& parent.getJSDocInfo().isJavaDispatch()) {
if (parent.getType() == Token.ASSIGN) {
Preconditions.checkState(parent.getLastChild() == n);
n.putBooleanProp(Node.IS_DISPATCHER, true);
}
}
}
/**
* In the AST that Rhino gives us, it needs to make a distinction
* between jsdoc on the object literal node and jsdoc on the object literal
* value. For example,
* <pre>
* var x = {
* / JSDOC /
* a: 'b',
* c: / JSDOC / 'd'
* };
* </pre>
*
* But in few narrow cases (in particular, function literals), it's
* a lot easier for us if the doc is attached to the value.
*/
private void normalizeObjectLiteralKeyAnnotations(
Node objlit, Node key, Node value) {
Preconditions.checkState(objlit.getType() == Token.OBJECTLIT);
if (key.getJSDocInfo() != null &&
value.getType() == Token.FUNCTION) {
value.setJSDocInfo(key.getJSDocInfo());
}
}
/**
* Annotate optional and var_arg function parameters.
*/
private void annotateFunctions(Node n, Node parent) {
JSDocInfo fnInfo = NodeUtil.getFunctionJSDocInfo(n);
// Compute which function parameters are optional and
// which are var_args.
Node args = n.getFirstChild().getNext();
for (Node arg = args.getFirstChild();
arg != null;
arg = arg.getNext()) {
String argName = arg.getString();
JSTypeExpression typeExpr = fnInfo == null ?
null : fnInfo.getParameterType(argName);
if (convention.isOptionalParameter(arg) ||
typeExpr != null && typeExpr.isOptionalArg()) {
arg.putBooleanProp(Node.IS_OPTIONAL_PARAM, true);
}
if (convention.isVarArgsParameter(arg) ||
typeExpr != null && typeExpr.isVarArgs()) {
arg.putBooleanProp(Node.IS_VAR_ARGS_PARAM, true);
}
}
}
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
*/
void expectBitwiseable(NodeTraversal t, Node n, JSType type, String msg) {
if (!type.matchesNumberContext() && !type.isSubtype(allValueTypes)) {
mismatch(t, n, msg, type, allValueTypes);
}
}
/**
* Expect the type to be a number or string, or a type convertible to a number
* or string. If the expectation is not met, issue a warning at the provided
* node's source code position.
*/
void expectStringOrNumber(
NodeTraversal t, Node n, JSType type, String msg) {
if (!type.matchesNumberContext() && !type.matchesStringContext()) {
mismatch(t, n, msg, type, NUMBER_STRING);
}
}
/**
* Expect the type to be anything but the null or void type. If the
* expectation is not met, issue a warning at the provided node's
* source code position. Note that a union type that includes the
* void type and at least one other type meets the expectation.
* @return Whether the expectation was met.
*/
boolean expectNotNullOrUndefined(
NodeTraversal t, Node n, JSType type, String msg, JSType expectedType) {
if (!type.isNoType() && !type.isUnknownType() &&
type.isSubtype(nullOrUndefined) &&
!containsForwardDeclaredUnresolvedName(type)) {
// There's one edge case right now that we don't handle well, and
// that we don't want to warn about.
// if (this.x == null) {
// this.initializeX();
// this.x.foo();
// }
// In this case, we incorrectly type x because of how we
// infer properties locally. See issue 109.
// http://code.google.com/p/closure-compiler/issues/detail?id=109
//
// We do not do this inference globally.
if (n.getType() == Token.GETPROP &&
!t.inGlobalScope() && type.isNullType()) {
return true;
}
mismatch(t, n, msg, type, expectedType);
return false;
}
return true;
}
private boolean containsForwardDeclaredUnresolvedName(JSType type) {
if (type instanceof UnionType) {
for (JSType alt : ((UnionType) type).getAlternates()) {
if (containsForwardDeclaredUnresolvedName(alt)) {
return true;
}
}
}
return type.isNoResolvedType();
}
/**
* Expect that the type of a switch condition matches the type of its
* case condition.
*/
void expectSwitchMatchesCase(NodeTraversal t, Node n, JSType switchType,
JSType caseType) {
// ECMA-262, page 68, step 3 of evaluation of CaseBlock,
// but allowing extra autoboxing.
// TODO(user): remove extra conditions when type annotations
// in the code base have adapted to the change in the compiler.
if (!switchType.canTestForShallowEqualityWith(caseType) &&
(caseType
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> The node where warnings should point to.
* @param superObject The expected super instance type.
* @param subObject The sub instance type.
*/
void expectSuperType(NodeTraversal t, Node n, ObjectType superObject,
ObjectType subObject) {
FunctionType subCtor = subObject.getConstructor();
ObjectType declaredSuper =
subObject.getImplicitPrototype().getImplicitPrototype();
if (!declaredSuper.equals(superObject)) {
if (declaredSuper.equals(getNativeType(OBJECT_TYPE))) {
if (shouldReport) {
compiler.report(
t.makeError(n, MISSING_EXTENDS_TAG_WARNING,
subObject.toString()));
}
registerMismatch(superObject, declaredSuper);
} else {
mismatch(t.getSourceName(), n,
"mismatch in declaration of superclass type",
superObject, declaredSuper);
}
// Correct the super type.
if (!subCtor.hasCachedValues()) {
subCtor.setPrototypeBasedOn(superObject);
}
}
}
/**
* Expect that the first type can be cast to the second type. The first type
* should be either a subtype or supertype of the second.
*
* @param t The node traversal.
* @param n The node where warnings should point.
* @param type The type being cast from.
* @param castType The type being cast to.
*/
void expectCanCast(NodeTraversal t, Node n, JSType type, JSType castType) {
castType = castType.restrictByNotNullOrUndefined();
type = type.restrictByNotNullOrUndefined();
if (!type.canAssignTo(castType) && !castType.canAssignTo(type)) {
if (shouldReport) {
compiler.report(
t.makeError(n, INVALID_CAST,
castType.toString(), type.toString()));
}
registerMismatch(type, castType);
}
}
/**
* Expect that the given variable has not been declared with a type.
*
* @param sourceName The name of the source file we're in.
* @param n The node where warnings should point to.
* @param parent The parent of {@code n}.
* @param var The variable that we're checking.
* @param variableName The name of the variable.
* @param newType The type being applied to the variable. Mostly just here
* for the benefit of the warning.
*/
void expectUndeclaredVariable(String sourceName, Node n, Node parent, Var var,
String variableName, JSType newType) {
boolean allowDupe = false;
if (n.getType() == Token.GETPROP ||
NodeUtil.isObjectLitKey(n, parent)) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
}
JSType varType = var.getType();
// Only report duplicate declarations that have types. Other duplicates
// will be reported by the syntactic scope creator later in the
//
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> compilation process.
if (varType != null &&
varType != typeRegistry.getNativeType(UNKNOWN_TYPE) &&
newType != null &&
newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) {
// If there are two typed declarations of the same variable, that
// is an error and the second declaration is ignored, except in the
// case of native types. A null input type means that the declaration
// was made in TypedScopeCreator#createInitialScope and is a
// native type.
if (var.input == null) {
n.setJSType(varType);
if (parent.getType() == Token.VAR) {
if (n.getFirstChild() != null) {
n.getFirstChild().setJSType(varType);
}
} else {
Preconditions.checkState(parent.getType() == Token.FUNCTION);
parent.setJSType(varType);
}
} else {
// Always warn about duplicates if the overridden type does not
// match the original type.
//
// If the types match, suppress the warning iff there was a @suppress
// tag, or if the original declaration was a stub.
if (!(allowDupe ||
var.getParentNode().getType() == Token.EXPR_RESULT) ||
!newType.equals(varType)) {
if (shouldReport) {
compiler.report(
JSError.make(sourceName, n, DUP_VAR_DECLARATION,
variableName, newType.toString(), var.getInputName(),
String.valueOf(var.nameNode.getLineno()),
varType.toString()));
}
}
}
}
}
/**
* Expect that all properties on interfaces that this type implements are
* implemented and correctly typed.
*/
void expectAllInterfaceProperties(NodeTraversal t, Node n,
FunctionType type) {
ObjectType instance = type.getInstanceType();
for (ObjectType implemented : type.getAllImplementedInterfaces()) {
if (implemented.getImplicitPrototype() != null) {
for (String prop :
implemented.getImplicitPrototype().getOwnPropertyNames()) {
expectInterfaceProperty(t, n, instance, implemented, prop);
}
}
}
}
/**
* Expect that the peroperty in an interface that this type implements is
* implemented and correctly typed.
*/
private void expectInterfaceProperty(NodeTraversal t, Node n,
ObjectType instance, ObjectType implementedInterface, String prop) {
if (!instance.hasProperty(prop)) {
// Not implemented
String sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
sourceName = sourceName == null ? "" : sourceName;
if (shouldReport) {
compiler.report(JSError.make(sourceName, n,
INTERFACE_METHOD_NOT_IMPLEMENTED,
prop, implementedInterface.toString(), instance.toString()));
}
registerMismatch(instance, implementedInterface);
} else {
JSType found = instance.getPropertyType(prop);
JSType required
= implementedInterface.getImplicitPrototype().getPropertyType(prop);
found = found.restrictByNotNullOrUndefined();
required = required.restrictByNotNullOrUndefined();
if (!found.canAssign
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> type of the node will be dereferenced
* to an Object type, if possible.
*/
String getReadableJSTypeName(Node n, boolean dereference) {
// If we're analyzing a GETPROP, the property may be inherited by the
// prototype chain. So climb the prototype chain and find out where
// the property was originally defined.
if (n.getType() == Token.GETPROP) {
ObjectType objectType = getJSType(n.getFirstChild()).dereference();
if (objectType != null) {
String propName = n.getLastChild().getString();
if (objectType.getConstructor() != null &&
objectType.getConstructor().isInterface()) {
objectType = FunctionType.getTopDefiningInterface(
objectType, propName);
} else {
// classes
while (objectType != null && !objectType.hasOwnProperty(propName)) {
objectType = objectType.getImplicitPrototype();
}
}
// Don't show complex function names or anonymous types.
// Instead, try to get a human-readable type name.
if (objectType != null &&
(objectType.getConstructor() != null ||
objectType.isFunctionPrototypeType())) {
return objectType.toString() + "." + propName;
}
}
}
JSType type = getJSType(n);
if (dereference) {
ObjectType dereferenced = type.dereference();
if (dereferenced != null) {
type = dereferenced;
}
}
String qualifiedName = n.getQualifiedName();
if (type.isFunctionPrototypeType() ||
(type.toObjectType() != null &&
type.toObjectType().getConstructor() != null)) {
return type.toString();
} else if (qualifiedName != null) {
return qualifiedName;
} else if (type instanceof FunctionType) {
// Don't show complex function names.
return "function";
} else {
return type.toString();
}
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(user): This branch indicates a compiler bug, not worthy of
// halting the compilation but we should log this and analyze to track
// down why it happens. This is not critical and will be resolved over
// time as the type checker is extended.
return getNativeType(UNKNOWN_TYPE);
} else {
return jsType;
}
}
private JSType getNativeType(JSTypeNative typeId) {
return typeRegistry.getNativeType(typeId);
}
/**
* Signals that the first type and the second type have been
* used interchangeably.
*
* Type-based optimizations should take this into account
* so that they don't wreck code with type warnings.
*/
static class TypeMismatch {
final JSType typeA;
final JSType typeB;
/**
* It's the responsibility of the class that creates the
* {@code TypeMismatch} to ensure that {@code a
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>no + charno : charno;
}
final String getString() { return string; }
final boolean eof() { return hitEOF; }
private String getStringFromBuffer() {
tokenEnd = cursor;
return new String(stringBuffer, 0, stringBufferTop);
}
private void addToString(int c) {
int N = stringBufferTop;
if (N == stringBuffer.length) {
char[] tmp = new char[stringBuffer.length * 2];
System.arraycopy(stringBuffer, 0, tmp, 0, N);
stringBuffer = tmp;
}
stringBuffer[N] = (char)c;
stringBufferTop = N + 1;
}
void ungetChar(int c) {
// can not unread past across line boundary
assert(!(ungetCursor != 0 && ungetBuffer[ungetCursor - 1] == '\n'));
ungetBuffer[ungetCursor++] = c;
cursor--;
}
private boolean matchChar(int test) {
int c = getCharIgnoreLineEnd();
if (c == test) {
tokenEnd = cursor;
return true;
} else {
ungetCharIgnoreLineEnd(c);
return false;
}
}
private static boolean isAlpha(int c) {
// Use 'Z' < 'a'
if (c <= 'Z') {
return 'A' <= c;
} else {
return 'a' <= c && c <= 'z';
}
}
private boolean isJSDocString(int c) {
switch (c) {
case '@':
case '*':
case ',':
case '>':
case ':':
case '(':
case ')':
case '{':
case '}':
case '[':
case ']':
case '?':
case '!':
case '|':
case '=':
case EOF_CHAR:
case '\n':
return false;
default:
return !isJSSpace(c);
}
}
/* As defined in ECMA. jsscan.c uses C isspace() (which allows
* \v, I think.) note that code in getChar() implicitly accepts
* '\r' == \u000D as well.
*/
static boolean isJSSpace(int c) {
if (c <= 127) {
return c == 0x20 || c == 0x9 || c == 0xC || c == 0xB;
} else {
return c == 0xA0
|| Character.getType((char)c) == Character.SPACE_SEPARATOR;
}
}
private static boolean isJSFormatChar(int c) {
return c > 127 && Character.getType((char)c) == Character.FORMAT;
}
/**
* Allows the JSDocParser to update the character offset
* so that getCharno() returns a valid character position.
*/
void update() {
charno = getOffset();
}
private int peekChar() {
int c = getChar();
ungetChar(c);
return c;
}
protected int getChar() {
if
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
* Copyright 2007 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Checks for certain uses of the {@code this} keyword that are considered
* unsafe because they are likely to reference the global {@code this} object
* unintentionally.
*
* <p>A use of {@code this} is considered unsafe if it's on the left side of an
* assignment or a property access, and not inside one of the following:
* <ol>
* <li>a prototype method
* <li>a function annotated with {@code @constructor}
* <li>a function annotated with {@code @this}.
* <li>a function where there's no logical place to put a
* {@code this} annotation.
* </ol>
*
* <p>Note that this check does not track assignments of {@code this} to
* variables or objects. The code
* <pre>
* function evil() {
* var a = this;
* a.useful = undefined;
* }
* </pre>
* will not get flagged, even though it is semantically equivalent to
* <pre>
* function evil() {
* this.useful = undefined;
* }
* </pre>
* which would get flagged.
*
*/
final class CheckGlobalThis implements Callback {
static final DiagnosticType GLOBAL_THIS = DiagnosticType.warning(
"JSC_USED_GLOBAL_THIS",
"dangerous use of the global 'this' object");
private final AbstractCompiler compiler;
/**
* If {@code assignLhsChild != null}, then the node being traversed is
* a descendant of the first child of an ASSIGN node. assignLhsChild's
* parent is this ASSIGN node.
*/
private Node assignLhsChild = null;
CheckGlobalThis(AbstractCompiler compiler) {
this.compiler = compiler;
}
/**
* Since this pass reports errors only when a global {@code this} keyword
* is encountered, there is no reason to traverse non global contexts.
*/
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
// Don't traverse functions that are constructors or have the @
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>this
// or @override annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends != null &&
maybeLends.getLendsName() != null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
}
if (parent != null && parent.getType() == Token.ASSIGN) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
// Only traverse the right side if it's not an assignment to a prototype
// property or subproperty.
if (NodeUtil.isGet(lhs)) {
if (lhs.getType() == Token.GETPROP &&
lhs.getLastChild().getString().equals("prototype")) {
return false;
}
Node llhs = lhs.getFirstChild();
if (llhs.getType() == Token.GETPROP &&
llhs.getLastChild().getString().equals("prototype")) {
return false;
}
}
}
}
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.THIS && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, GLOBAL_THIS));
}
if (n == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
return parent != null && NodeUtil.isGet(parent);
}
/**
* Gets a function's JSDoc
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
* ... x = function() {};
* var ... x = function() {};
* ... var x = function() {};
* </pre>
*/
private JSDocInfo getFunctionJsDocInfo(Node n) {
JSDocInfo jsDoc = n.getJSDocInfo();
Node parent = n.getParent();
if (jsDoc == null) {
int parentType = parent.getType();
if (parentType == Token.NAME || parentType == Token.ASSIGN) {
jsDoc = parent.getJSDocInfo();
if (jsDoc == null && parentType == Token.NAME) {
Node gramps = parent.getParent();
if (gramps.getType() == Token.VAR) {
jsDoc = gramps.getJSDocInfo();
}
}
}
}
return jsDoc;
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>, simply return the object type.
if (isEmpty) {
return registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE);
}
return registry.createRecordType(Collections.unmodifiableMap(properties));
}
static class RecordProperty {
private final JSType type;
private final Node propertyNode;
RecordProperty(JSType type, Node propertyNode) {
this.type = type;
this.propertyNode = propertyNode;
}
public JSType getType() {
return type;
}
public Node getPropertyNode() {
return propertyNode;
}
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>_DEFINE_INIT_ERROR =
DiagnosticType.error(
"JSC_NON_GLOBAL_DEFINE_INIT_ERROR",
"@define variable {0} assignment must be global");
static final DiagnosticType DEFINE_NOT_ASSIGNABLE_ERROR =
DiagnosticType.error(
"@define variable cannot be assigned here",
"@define variable {0} cannot be assigned due to unsafe code at {1}.");
private static final MessageFormat REASON_DEFINE_NOT_ASSIGNABLE =
new MessageFormat("line {0} of {1}");
/**
* Create a pass that overrides define constants.
*
* TODO(nicksantos): Write a builder to help JSCompiler induce
* {@code replacements} from command-line flags
*
* @param replacements A hash table of names of defines to their replacements.
* All replacements <b>must</b> be literals.
*/
ProcessDefines(AbstractCompiler compiler, Map<String, Node> replacements) {
this.compiler = compiler;
dominantReplacements = replacements;
}
/**
* Injects a pre-computed global namespace, so that the same namespace
* can be re-used for multiple check passes. Returns {@code this} for
* easy chaining.
*/
ProcessDefines injectNamespace(GlobalNamespace namespace) {
this.namespace = namespace;
return this;
}
public void process(Node externs, Node root) {
if (namespace == null) {
namespace = new GlobalNamespace(compiler, root);
}
overrideDefines(collectDefines(root, namespace));
}
private void overrideDefines(Map<String, DefineInfo> allDefines) {
boolean changed = false;
for (Map.Entry<String, DefineInfo> def : allDefines.entrySet()) {
String defineName = def.getKey();
DefineInfo info = def.getValue();
Node inputValue = dominantReplacements.get(defineName);
Node finalValue = inputValue != null ?
inputValue : info.getLastValue();
if (finalValue != info.initialValue) {
info.initialValueParent.replaceChild(
info.initialValue, finalValue.cloneTree());
compiler.addToDebugLog("Overriding @define variable " + defineName);
changed = changed ||
finalValue.getType() != info.initialValue.getType() ||
!finalValue.isEquivalentTo(info.initialValue);
}
}
if (changed) {
compiler.reportCodeChange();
}
Set<String> unusedReplacements = dominantReplacements.keySet();
unusedReplacements.removeAll(allDefines.keySet());
unusedReplacements.removeAll(KNOWN_DEFINES);
for (String unknownDefine : unusedReplacements) {
compiler.report(JSError.make(UNKNOWN_DEFINE_WARNING, unknownDefine));
}
}
private static String format(MessageFormat format, Object... params) {
return format.format(params);
}
/**
* Only defines of literal number, string, or boolean are supported.
*/
private boolean isValidDefineType(JSTypeExpression expression) {
JSType type = expression.evaluate(null, compiler.getTypeRegistry());
return !type.isUnknownType() && type.is
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>Subtype(
compiler.getTypeRegistry().getNativeType(
JSTypeNative.NUMBER_STRING_BOOLEAN));
}
/**
* Finds all defines, and creates a {@link DefineInfo} data structure for
* each one.
* @return A map of {@link DefineInfo} structures, keyed by name.
*/
private Map<String, DefineInfo> collectDefines(Node root,
GlobalNamespace namespace) {
// Find all the global names with a @define annotation
List<Name> allDefines = Lists.newArrayList();
for (Name name : namespace.getNameIndex().values()) {
if (name.docInfo != null && name.docInfo.isDefine()) {
// Process defines should not depend on check types being enabled,
// so we look for the JSDoc instead of the inferred type.
if (isValidDefineType(name.docInfo.getType())) {
allDefines.add(name);
} else {
JSError error = JSError.make(
name.declaration.getSourceName(),
name.declaration.node,
INVALID_DEFINE_TYPE_ERROR);
compiler.report(error);
}
} else {
for (Ref ref : name.getRefs()) {
if (ref == name.declaration) {
// Declarations were handled above.
continue;
}
Node n = ref.node;
Node parent = ref.node.getParent();
JSDocInfo info = n.getJSDocInfo();
if (info == null &&
parent.getType() == Token.VAR && parent.hasOneChild()) {
info = parent.getJSDocInfo();
}
if (info != null && info.isDefine()) {
allDefines.add(name);
break;
}
}
}
}
CollectDefines pass = new CollectDefines(compiler, allDefines);
NodeTraversal.traverse(compiler, root, pass);
return pass.getAllDefines();
}
/**
* Finds all assignments to @defines, and figures out the last value of
* the @define.
*/
private static final class CollectDefines implements Callback {
private final AbstractCompiler compiler;
private final Map<String, DefineInfo> assignableDefines;
private final Map<String, DefineInfo> allDefines;
private final Map<Node, RefInfo> allRefInfo;
// A hack that allows us to remove ASSIGN/VAR statements when
// we're currently visiting one of the children of the assign.
private Node lvalueToRemoveLater = null;
// A stack tied to the node traversal, to keep track of whether
// we're in a conditional block. If 1 is at the top, assignment to
// a define is allowed. Otherwise, it's not allowed.
private final Deque<Integer> assignAllowed;
CollectDefines(AbstractCompiler compiler, List<Name> listOfDefines) {
this.compiler = compiler;
this.allDefines = Maps.newHashMap();
assignableDefines = Maps.newHashMap();
assignAllowed = new ArrayDeque<Integer>();
assignAllowed.push(1);
// Create a map of references to defines keyed by node for easy lookup
allRefInfo = Maps.newHashMap();
for (Name name : listOfDefines) {
if (name.declaration != null
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>) {
allRefInfo.put(name.declaration.node,
new RefInfo(name.declaration, name));
}
for (Ref ref : name.getRefs()) {
if (ref == name.declaration) {
// Declarations were handled above.
continue;
}
// If there's a TWIN def, only put one of the twins in.
if (ref.getTwin() == null || !ref.getTwin().isSet()) {
allRefInfo.put(ref.node, new RefInfo(ref, name));
}
}
}
}
/**
* Get a map of {@link DefineInfo} structures, keyed by the name of
* the define.
*/
Map<String, DefineInfo> getAllDefines() {
return allDefines;
}
/**
* Keeps track of whether the traversal is in a conditional branch.
* We traverse all nodes of the parse tree.
*/
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
updateAssignAllowedStack(n, true);
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
RefInfo refInfo = allRefInfo.get(n);
if (refInfo != null) {
Ref ref = refInfo.ref;
Name name = refInfo.name;
String fullName = name.fullName();
switch (ref.type) {
case SET_FROM_GLOBAL:
case SET_FROM_LOCAL:
Node valParent = getValueParent(ref);
Node val = valParent.getLastChild();
if (valParent.getType() == Token.ASSIGN && name.isSimpleName() &&
name.declaration == ref) {
// For defines, it's an error if a simple name is assigned
// before it's declared
compiler.report(
t.makeError(val, INVALID_DEFINE_INIT_ERROR, fullName));
} else if (processDefineAssignment(t, fullName, val, valParent)) {
// remove the assignment so that the variable is still declared,
// but no longer assigned to a value, e.g.,
// DEF_FOO = 5; // becomes "5;"
// We can't remove the ASSIGN/VAR when we're still visiting its
// children, so we'll have to come back later to remove it.
refInfo.name.removeRef(ref);
lvalueToRemoveLater = valParent;
}
break;
default:
if (t.inGlobalScope()) {
// Treat this as a reference to a define in the global scope.
// After this point, the define must not be reassigned,
// or it's an error.
DefineInfo info = assignableDefines.get(fullName);
if (info != null) {
setDefineInfoNotAssignable(info, t);
assignableDefines.remove(fullName);
}
}
break;
}
}
if (!t.inGlobalScope() &&
n.getJSDocInfo() != null && n.getJSDocInfo().isDefine()) {
// warn about @define annotations in local scopes
compiler.report(
t.makeError(n, NON_GLOBAL_DEFINE_INIT
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>_ERROR, ""));
}
if (lvalueToRemoveLater == n) {
lvalueToRemoveLater = null;
if (n.getType() == Token.ASSIGN) {
Node last = n.getLastChild();
n.removeChild(last);
parent.replaceChild(n, last);
} else {
Preconditions.checkState(n.getType() == Token.NAME);
n.removeChild(n.getFirstChild());
}
compiler.reportCodeChange();
}
if (n.getType() == Token.CALL) {
if (t.inGlobalScope()) {
// If there's a function call in the global scope,
// we just say it's unsafe and freeze all the defines.
//
// NOTE(nicksantos): We could be a lot smarter here. For example,
// ReplaceOverriddenVars keeps a call graph of all functions and
// which functions/variables that they reference, and tries
// to statically determine which functions are "safe" and which
// are not. But this would be overkill, expecially because
// the intended use of defines is with config_files, where
// all the defines are at the top of the bundle.
for (DefineInfo info : assignableDefines.values()) {
setDefineInfoNotAssignable(info, t);
}
assignableDefines.clear();
}
}
updateAssignAllowedStack(n, false);
}
/**
* Determines whether assignment to a define should be allowed
* in the subtree of the given node, and if not, records that fact.
*
* @param n The node whose subtree we're about to enter or exit.
* @param entering True if we're entering the subtree, false otherwise.
*/
private void updateAssignAllowedStack(Node n, boolean entering) {
switch (n.getType()) {
case Token.CASE:
case Token.FOR:
case Token.FUNCTION:
case Token.HOOK:
case Token.IF:
case Token.SWITCH:
case Token.WHILE:
if (entering) {
assignAllowed.push(0);
} else {
assignAllowed.remove();
}
break;
}
}
/**
* Determines whether assignment to a define should be allowed
* at the current point of the traversal.
*/
private boolean isAssignAllowed() {
return assignAllowed.element() == 1;
}
/**
* Tracks the given define.
*
* @param t The current traversal, for context.
* @param name The full name for this define.
* @param value The value assigned to the define.
* @param valueParent The parent node of value.
* @return Whether we should remove this assignment from the parse tree.
*/
private boolean processDefineAssignment(NodeTraversal t,
String name, Node value, Node valueParent) {
if (value == null || !NodeUtil.isValidDefineValue(value,
allDefines.keySet())) {
compiler.report(
t.makeError(value, INVALID_DEFINE_INIT_ERROR, name));
} else if (!isAssignAllowed()) {
compiler.report(
t.makeError(valueParent, NON_GLOBAL_DEFINE_INIT_ERROR, name));
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> else {
DefineInfo info = allDefines.get(name);
if (info == null) {
// First declaration of this define.
info = new DefineInfo(value, valueParent);
allDefines.put(name, info);
assignableDefines.put(name, info);
} else if (info.recordAssignment(value)) {
// The define was already initialized, but this is a safe
// re-assignment.
return true;
} else {
// The define was already initialized, and this is an unsafe
// re-assignment.
compiler.report(
t.makeError(valueParent, DEFINE_NOT_ASSIGNABLE_ERROR,
name, info.getReasonWhyNotAssignable()));
}
}
return false;
}
/**
* Gets the parent node of the value for any assignment to a Name.
* For example, in the assignment
* {@code var x = 3;}
* the parent would be the NAME node.
*/
private static Node getValueParent(Ref ref) {
// there are two types of declarations: VARs and ASSIGNs
return ref.node.getParent() != null &&
ref.node.getParent().getType() == Token.VAR ?
ref.node : ref.node.getParent();
}
/**
* Records the fact that because of the current node in the node traversal,
* the define can't ever be assigned again.
*
* @param info Represents the define variable.
* @param t The current traversal.
*/
private void setDefineInfoNotAssignable(DefineInfo info, NodeTraversal t) {
info.setNotAssignable(format(REASON_DEFINE_NOT_ASSIGNABLE,
t.getLineNumber(), t.getSourceName()));
}
/**
* A simple data structure for associating a Ref with the name
* that it references.
*/
private static class RefInfo {
final Ref ref;
final Name name;
RefInfo(Ref ref, Name name) {
this.ref = ref;
this.name = name;
}
}
}
/**
* A simple class for storing information about a define.
* Gathers the initial value, the last assigned value, and whether
* the define can be safely assigned a new value.
*/
private static final class DefineInfo {
public final Node initialValueParent;
public final Node initialValue;
private Node lastValue;
private boolean isAssignable;
private String reasonNotAssignable;
/**
* Initializes a define.
*/
public DefineInfo(Node initialValue, Node initialValueParent) {
this.initialValueParent = initialValueParent;
this.initialValue = initialValue;
lastValue = initialValue;
isAssignable = true;
}
/**
* Records the fact that this define can't be assigned a value anymore.
*
* @param reason A message describing the reason why it can't be assigned.
*/
public void setNotAssignable(String reason) {
isAssignable = false;
reasonNotAssignable = reason;
}
/**
* Gets the reason why a define is not assignable.
*/
public String getReasonWhyNotAssignable() {
return reasonNotAssignable;
}
/**
* Records an assigned value.
*
* @
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>flowableVars) {
String name = unflowableVar.getName();
if (functionScope.getVar(name) == unflowableVar) {
this.unflowableVarNames.add(name);
}
}
// For each local variable declared with the VAR keyword, the entry
// type is VOID.
Iterator<Var> varIt =
functionScope.getDeclarativelyUnboundVarsWithoutTypes();
while (varIt.hasNext()) {
Var var = varIt.next();
if (this.unflowableVarNames.contains(var.getName())) {
continue;
}
this.functionScope.inferSlotType(
var.getName(), getNativeType(VOID_TYPE));
}
this.bottomScope = LinkedFlowScope.createEntryLattice(
new Scope(functionScope.getRootNode(), functionScope.getTypeOfThis()));
}
@Override
FlowScope createInitialEstimateLattice() {
return bottomScope;
}
@Override
FlowScope createEntryLattice() {
return functionScope;
}
/**
* @return Local variables assigned in this scope, but which are declared in
* a scope outside of it. Hashed by the scope they're declared in.
*/
Multimap<Scope, Var> getAssignedOuterLocalVars() {
return assignedOuterLocalVars;
}
@Override
FlowScope flowThrough(Node n, FlowScope input) {
// If we have not walked a path from <entry> to <n>, then we don't
// want to infer anything about this scope.
if (input == bottomScope) {
return input;
}
FlowScope output = input.createChildFlowScope();
output = traverse(n, output);
return output;
}
@Override
@SuppressWarnings("fallthrough")
List<FlowScope> branchedFlowThrough(Node source, FlowScope input) {
// NOTE(nicksantos): Right now, we just treat ON_EX edges like UNCOND
// edges. If we wanted to be perfect, we'd actually JOIN all the out
// lattices of this flow with the in lattice, and then make that the out
// lattice for the ON_EX edge. But it's probably to expensive to be
// worthwhile.
FlowScope output = flowThrough(source, input);
Node condition = null;
FlowScope conditionFlowScope = null;
BooleanOutcomePair conditionOutcomes = null;
List<DiGraphEdge<Node, Branch>> branchEdges = getCfg().getOutEdges(source);
List<FlowScope> result = Lists.newArrayListWithCapacity(branchEdges.size());
for (DiGraphEdge<Node, Branch> branchEdge : branchEdges) {
Branch branch = branchEdge.getValue();
FlowScope newScope = output;
switch (branch) {
case ON_TRUE:
if (NodeUtil.isForIn(source)) {
// item is assigned a property name, so its type should be string.
Node item = source.getFirstChild();
Node obj = item.getNext();
FlowScope informed = traverse(obj, output.createChildFlowScope());
if (item.getType() == Token.VAR) {
item = item.getFirstChild();
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
}
if (item.getType() == Token.NAME) {
JSType iterKeyType = getNativeType(STRING_TYPE);
ObjectType objType = getJSType(obj).dereference();
JSType objIndexType = objType == null ?
null : objType.getIndexType();
if (objIndexType != null && !objIndexType.isUnknownType()) {
JSType narrowedKeyType =
iterKeyType.getGreatestSubtype(objIndexType);
if (!narrowedKeyType.isEmptyType()) {
iterKeyType = narrowedKeyType;
}
}
redeclare(informed, item.getString(), iterKeyType);
}
newScope = informed;
break;
}
// FALL THROUGH
case ON_FALSE:
if (condition == null) {
condition = NodeUtil.getConditionExpression(source);
if (condition == null && source.getType() == Token.CASE) {
condition = source;
// conditionFlowScope is cached from previous iterations
// of the loop.
if (conditionFlowScope == null) {
conditionFlowScope = traverse(
condition.getFirstChild(), output.createChildFlowScope());
}
}
}
if (condition != null) {
if (condition.getType() == Token.AND ||
condition.getType() == Token.OR) {
// When handling the short-circuiting binary operators,
// the outcome scope on true can be different than the outcome
// scope on false.
//
// TODO(nicksantos): The "right" way to do this is to
// carry the known outcome all the way through the
// recursive traversal, so that we can construct a
// different flow scope based on the outcome. However,
// this would require a bunch of code and a bunch of
// extra computation for an edge case. This seems to be
// a "good enough" approximation.
// conditionOutcomes is cached from previous iterations
// of the loop.
if (conditionOutcomes == null) {
conditionOutcomes = condition.getType() == Token.AND ?
traverseAnd(condition, output.createChildFlowScope()) :
traverseOr(condition, output.createChildFlowScope());
}
newScope =
reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
condition,
conditionOutcomes.getOutcomeFlowScope(
condition.getType(), branch == Branch.ON_TRUE),
branch == Branch.ON_TRUE);
} else {
// conditionFlowScope is cached from previous iterations
// of the loop.
if (conditionFlowScope == null) {
conditionFlowScope =
traverse(condition, output.createChildFlowScope());
}
newScope =
reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
condition, conditionFlowScope, branch == Branch.ON_TRUE);
}
}
break;
}
result.add(newScope.optimize());
}
return result;
}
private FlowScope traverse(Node n, FlowScope scope) {
switch (n.getType()) {
case Token.ASSIGN:
scope = traverseAssign(n, scope);
break;
case Token.NAME:
scope = traverseName(n, scope);
break;
case Token.GET
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>PROP:
scope = traverseGetProp(n, scope);
break;
case Token.AND:
scope = traverseAnd(n, scope).getJoinedFlowScope()
.createChildFlowScope();
break;
case Token.OR:
scope = traverseOr(n, scope).getJoinedFlowScope()
.createChildFlowScope();
break;
case Token.HOOK:
scope = traverseHook(n, scope);
break;
case Token.OBJECTLIT:
scope = traverseObjectLiteral(n, scope);
break;
case Token.CALL:
scope = traverseCall(n, scope);
break;
case Token.NEW:
scope = traverseNew(n, scope);
break;
case Token.ASSIGN_ADD:
case Token.ADD:
scope = traverseAdd(n, scope);
break;
case Token.POS:
case Token.NEG:
scope = traverse(n.getFirstChild(), scope); // Find types.
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.ARRAYLIT:
scope = traverseArrayLiteral(n, scope);
break;
case Token.THIS:
n.setJSType(scope.getTypeOfThis());
break;
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
case Token.URSH:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_MUL:
case Token.ASSIGN_SUB:
case Token.DIV:
case Token.MOD:
case Token.BITAND:
case Token.BITXOR:
case Token.BITOR:
case Token.MUL:
case Token.SUB:
case Token.DEC:
case Token.INC:
case Token.BITNOT:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.LP:
case Token.GET_REF:
scope = traverse(n.getFirstChild(), scope);
n.setJSType(getJSType(n.getFirstChild()));
break;
case Token.COMMA:
scope = traverseChildren(n, scope);
n.setJSType(getJSType(n.getLastChild()));
break;
case Token.TYPEOF:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(STRING_TYPE));
break;
case Token.DELPROP:
case Token.LT:
case Token.LE:
case Token.GT:
case Token.GE:
case Token.NOT:
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.INSTANCEOF:
case Token.IN:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.GETELEM:
scope = traverseGetElem(n, scope
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>);
break;
case Token.EXPR_RESULT:
scope = traverseChildren(n, scope);
if (n.getFirstChild().getType() == Token.GETPROP) {
ensurePropertyDeclared(n.getFirstChild());
}
break;
case Token.SWITCH:
scope = traverse(n.getFirstChild(), scope);
break;
case Token.VAR:
case Token.RETURN:
case Token.THROW:
scope = traverseChildren(n, scope);
break;
case Token.CATCH:
scope = traverseCatch(n, scope);
break;
}
if (n.getType() != Token.FUNCTION) {
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.hasType()) {
JSType castType = info.getType().evaluate(syntacticScope, registry);
// A stubbed type cast on a qualified name should take
// effect for all subsequent accesses of that name,
// so treat it the same as an assign to that name.
if (n.isQualifiedName() &&
n.getParent().getType() == Token.EXPR_RESULT) {
updateScopeForTypeChange(scope, n, n.getJSType(), castType);
}
n.setJSType(castType);
}
}
return scope;
}
/**
* Any value can be thrown, so it's really impossible to determine the type
* of a CATCH param. Treat it as the UNKNOWN type.
*/
private FlowScope traverseCatch(Node n, FlowScope scope) {
Node name = n.getFirstChild();
JSType type = getNativeType(JSTypeNative.UNKNOWN_TYPE);
name.setJSType(type);
redeclare(scope, name.getString(), type);
return scope;
}
private FlowScope traverseAssign(Node n, FlowScope scope) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
scope = traverseChildren(n, scope);
JSType leftType = left.getJSType();
JSType rightType = getJSType(right);
n.setJSType(rightType);
updateScopeForTypeChange(scope, left, leftType, rightType);
return scope;
}
/**
* Updates the scope according to the result of a type change, like
* an assignment or a type cast.
*/
private void updateScopeForTypeChange(
FlowScope scope, Node left, JSType leftType, JSType resultType) {
Preconditions.checkNotNull(resultType);
switch (left.getType()) {
case Token.NAME:
String varName = left.getString();
Var var = syntacticScope.getVar(varName);
if (var != null && var.isLocal() && var.getScope() != syntacticScope) {
assignedOuterLocalVars.put(var.getScope(), var);
}
// When looking at VAR initializers for declared VARs, we trust
// the declared type over the type it's being initialized to.
// This has two purposes:
// 1) We avoid re-declaring declared variables so that built-in
// types defined in externs are not redeclared.
// 2) When there's a lex
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>ical closure like
// /** @type {?string} */ var x = null;
// function f() { x = 'xyz'; }
// the inference will ignore the lexical closure,
// which is just wrong. This bug needs to be fixed eventually.
boolean isVarDeclaration = left.hasChildren();
if (!isVarDeclaration || var == null || var.isTypeInferred()) {
redeclare(scope, varName, resultType);
}
left.setJSType(isVarDeclaration || leftType == null ?
resultType : null);
if (var != null && var.isTypeInferred()) {
JSType oldType = var.getType();
var.setType(oldType == null ?
resultType : oldType.getLeastSupertype(resultType));
}
break;
case Token.GETPROP:
String qualifiedName = left.getQualifiedName();
if (qualifiedName != null) {
scope.inferQualifiedSlot(qualifiedName,
leftType == null ? getNativeType(UNKNOWN_TYPE) : leftType,
resultType);
}
left.setJSType(resultType);
ensurePropertyDefined(left, resultType);
break;
}
}
/**
* Defines a property if the property has not been defined yet.
*/
private void ensurePropertyDefined(Node getprop, JSType rightType) {
String propName = getprop.getLastChild().getString();
JSType nodeType = getJSType(getprop.getFirstChild());
ObjectType objectType = ObjectType.cast(
nodeType.restrictByNotNullOrUndefined());
if (objectType == null) {
registry.registerPropertyOnType(propName, nodeType);
} else {
if (ensurePropertyDeclaredHelper(getprop, objectType)) {
return;
}
if (!objectType.isPropertyTypeDeclared(propName)) {
// We do not want a "stray" assign to define an inferred property
// for every object of this type in the program. So we use a heuristic
// approach to determine whether to infer the propery.
//
// 1) If the property is already defined, join it with the previously
// inferred type.
// 2) If this isn't an instance object, define it.
// 3) If the property of an object is being assigned in the constructor,
// define it.
// 4) If this is a stub, define it.
// 5) Otherwise, do not define the type, but declare it in the registry
// so that we can use it for missing property checks.
if (objectType.hasProperty(propName) ||
!objectType.isInstanceType()) {
if ("prototype".equals(propName)) {
objectType.defineDeclaredProperty(
propName, rightType, false, getprop);
} else {
objectType.defineInferredProperty(
propName, rightType, false, getprop);
}
} else {
if (getprop.getFirstChild().getType() == Token.THIS &&
getJSType(syntacticScope.getRootNode()).isConstructor()) {
objectType.defineInferredProperty(
propName, rightType, false, getprop);
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
} else {
registry.registerPropertyOnType(propName, objectType);
}
}
}
}
}
/**
* Defines a declared property if it has not been defined yet.
*
* This handles the case where a property is declared on an object where
* the object type is inferred, and so the object type will not
* be known in {@code TypedScopeCreator}.
*/
private void ensurePropertyDeclared(Node getprop) {
ObjectType ownerType = ObjectType.cast(
getJSType(getprop.getFirstChild()).restrictByNotNullOrUndefined());
if (ownerType != null) {
ensurePropertyDeclaredHelper(getprop, ownerType);
}
}
/**
* Declares a property on its owner, if necessary.
* @return True if a property was declared.
*/
private boolean ensurePropertyDeclaredHelper(
Node getprop, ObjectType objectType) {
String propName = getprop.getLastChild().getString();
String qName = getprop.getQualifiedName();
if (qName != null) {
Var var = syntacticScope.getVar(qName);
if (var != null && !var.isTypeInferred()) {
// Handle normal declarations that could not be addressed earlier.
if (propName.equals("prototype") ||
// Handle prototype declarations that could not be addressed earlier.
(!objectType.hasOwnProperty(propName) &&
(!objectType.isInstanceType() ||
(var.isExtern() && !objectType.isNativeObjectType())))) {
return objectType.defineDeclaredProperty(
propName, var.getType(), var.isExtern(), getprop);
}
}
}
return false;
}
private FlowScope traverseName(Node n, FlowScope scope) {
String varName = n.getString();
Node value = n.getFirstChild();
JSType type = n.getJSType();
if (value != null) {
scope = traverse(value, scope);
updateScopeForTypeChange(scope, n, n.getJSType() /* could be null */,
getJSType(value));
return scope;
} else {
StaticSlot<JSType> var = scope.getSlot(varName);
if (var != null) {
// There are two situations where we don't want to use type information
// from the scope, even if we have it.
// 1) The var is escaped in a weird way, e.g.,
// function f() { var x = 3; function g() { x = null } (x); }
boolean isInferred = var.isTypeInferred();
boolean unflowable =
isInferred && unflowableVarNames.contains(varName);
// 2) We're reading type information from another scope for an
// inferred variable.
// var t = null; function f() { (t); }
boolean nonLocalInferredSlot =
isInferred &&
syntacticScope.getParent() != null &&
var == syntacticScope.getParent().getSlot(varName);
if (!unflowable && !nonLocalInferredSlot) {
type = var.getType();
if (type == null) {
type = getNativeType(UNKNOWN_TYPE);
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
} else {
type = registry.createUnionType(STRING_TYPE, NUMBER_TYPE);
}
}
n.setJSType(type);
if (n.getType() == Token.ASSIGN_ADD) {
updateScopeForTypeChange(scope, left, leftType, type);
}
return scope;
}
private boolean isAddedAsNumber(JSType type) {
return type.isSubtype(registry.createUnionType(VOID_TYPE, NULL_TYPE,
NUMBER_VALUE_OR_OBJECT_TYPE, BOOLEAN_TYPE, BOOLEAN_OBJECT_TYPE));
}
private FlowScope traverseHook(Node n, FlowScope scope) {
Node condition = n.getFirstChild();
Node trueNode = condition.getNext();
Node falseNode = n.getLastChild();
// verify the condition
scope = traverse(condition, scope);
// reverse abstract interpret the condition to produce two new scopes
FlowScope trueScope = reverseInterpreter.
getPreciserScopeKnowingConditionOutcome(
condition, scope, true);
FlowScope falseScope = reverseInterpreter.
getPreciserScopeKnowingConditionOutcome(
condition, scope, false);
// traverse the true node with the trueScope
traverse(trueNode, trueScope.createChildFlowScope());
// traverse the false node with the falseScope
traverse(falseNode, falseScope.createChildFlowScope());
// meet true and false nodes' types and assign
JSType trueType = trueNode.getJSType();
JSType falseType = falseNode.getJSType();
if (trueType != null && falseType != null) {
n.setJSType(trueType.getLeastSupertype(falseType));
} else {
n.setJSType(null);
}
return scope.createChildFlowScope();
}
private FlowScope traverseCall(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
Node left = n.getFirstChild();
JSType functionType = getJSType(left).restrictByNotNullOrUndefined();
if (functionType != null) {
if (functionType instanceof FunctionType) {
FunctionType fnType = (FunctionType) functionType;
n.setJSType(fnType.getReturnType());
updateTypeOfParameters(n, fnType);
updateTypeOfThisOnClosure(n, fnType);
} else if (functionType.equals(getNativeType(CHECKED_UNKNOWN_TYPE))) {
n.setJSType(getNativeType(CHECKED_UNKNOWN_TYPE));
}
}
scope = tightenTypesAfterAssertions(scope, n);
return scope;
}
private FlowScope tightenTypesAfterAssertions(FlowScope scope,
Node callNode) {
Node left = callNode.getFirstChild();
Node firstParam = left.getNext();
AssertionFunctionSpec assertionFunctionSpec =
assertionFunctionsMap.get(left.getQualifiedName());
if (assertionFunctionSpec == null || firstParam == null) {
return scope;
}
Node assertedNode = assertionFunctionSpec.getAssertedParam(firstParam);
if (assertedNode == null) {
return scope;
}
JSTypeNative assertedType = assertionFunctionSpec.getAssertedType();
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> String assertedNodeName = assertedNode.getQualifiedName();
// Handle assertions that enforce expressions evaluate to true.
if (assertedType == null) {
if (assertedNodeName != null) {
JSType type = getJSType(assertedNode);
JSType narrowed = type.restrictByNotNullOrUndefined();
if (type != narrowed) {
scope = scope.createChildFlowScope();
redeclare(scope, assertedNodeName, narrowed);
callNode.setJSType(narrowed);
}
} else if (assertedNode.getType() == Token.AND ||
assertedNode.getType() == Token.OR) {
BooleanOutcomePair conditionOutcomes =
traverseWithinShortCircuitingBinOp(assertedNode, scope);
scope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
assertedNode, conditionOutcomes.getOutcomeFlowScope(
assertedNode.getType(), true), true);
}
} else if (assertedNodeName != null) {
// Handle assertions that enforce expressions are of a certain type.
JSType type = getJSType(assertedNode);
JSType narrowed = type.getGreatestSubtype(getNativeType(assertedType));
if (type != narrowed) {
scope = scope.createChildFlowScope();
redeclare(scope, assertedNodeName, narrowed);
callNode.setJSType(narrowed);
}
}
return scope;
}
/**
* For functions with function parameters, type inference will set the type of
* a function literal argument from the function parameter type.
*/
private void updateTypeOfParameters(Node n, FunctionType fnType) {
int i = 0;
int childCount = n.getChildCount();
for (Node iParameter : fnType.getParameters()) {
if (i + 1 >= childCount) {
// TypeCheck#visitParametersList will warn so we bail.
return;
}
JSType iParameterType = iParameter.getJSType();
Node iArgument = n.getChildAtIndex(i + 1);
JSType iArgumentType = getJSType(iArgument);
inferPropertyTypesToMatchConstraint(iArgumentType, iParameterType);
if (iParameterType instanceof FunctionType) {
FunctionType iParameterFnType = (FunctionType) iParameterType;
if (iArgument.getType() == Token.FUNCTION &&
iArgumentType instanceof FunctionType &&
iArgument.getJSDocInfo() == null) {
iArgument.setJSType(iParameterFnType);
}
}
i++;
}
}
/**
* For functions with function(this: T, ...) and T as parameters, type
* inference will set the type of this on a function literal argument to the
* the actual type of T.
*/
private void updateTypeOfThisOnClosure(Node n, FunctionType fnType) {
// TODO(user): Make the template logic more general.
if (fnType.getTemplateTypeName() == null) {
return;
}
int i = 0;
int childCount = n.getChildCount();
// Find the parameter whose type is the template type.
for (Node iParameter : fnType.getParameters()) {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
JSType iParameterType =
getJSType(iParameter).restrictByNotNullOrUndefined();
if (iParameterType.isTemplateType()) {
// Find the actual type of this argument.
JSType iArgumentType = null;
if (i + 1 < childCount) {
Node iArgument = n.getChildAtIndex(i + 1);
iArgumentType = getJSType(iArgument).restrictByNotNullOrUndefined();
if (!(iArgumentType instanceof ObjectType)) {
compiler.report(
JSError.make(NodeUtil.getSourceName(iArgument), iArgument,
TEMPLATE_TYPE_NOT_OBJECT_TYPE));
return;
}
}
// Find the parameter whose type is function(this: T, ...)
boolean foundTemplateTypeOfThisParameter = false;
int j = 0;
for (Node jParameter : fnType.getParameters()) {
JSType jParameterType =
getJSType(jParameter).restrictByNotNullOrUndefined();
if (jParameterType instanceof FunctionType) {
FunctionType jParameterFnType = (FunctionType) jParameterType;
if (jParameterFnType.getTypeOfThis().equals(iParameterType)) {
foundTemplateTypeOfThisParameter = true;
// Find the actual type of the this argument.
if (j + 1 >= childCount) {
// TypeCheck#visitParameterList will warn so we bail.
return;
}
Node jArgument = n.getChildAtIndex(j + 1);
JSType jArgumentType = getJSType(jArgument);
if (jArgument.getType() == Token.FUNCTION &&
jArgumentType instanceof FunctionType) {
if (iArgumentType != null &&
// null and undefined get filtered out above.
!iArgumentType.isNoType()) {
// If it's an function expression, update the type of this
// using the actual type of T.
FunctionType jArgumentFnType = (FunctionType) jArgumentType;
if (jArgumentFnType.getTypeOfThis().isUnknownType()) {
// The new type will be picked up when we traverse the inner
// function.
jArgument.setJSType(
registry.createFunctionTypeWithNewThisType(
jArgumentFnType, (ObjectType) iArgumentType));
}
// Warn if the anonymous function literal does not
// reference this.
if (!NodeUtil.referencesThis(
NodeUtil.getFunctionBody(jArgument))) {
compiler.report(JSError.make(NodeUtil.getSourceName(n), n,
FUNCTION_LITERAL_UNREAD_THIS));
}
} else {
// Warn if the anonymous function literal references this.
if (NodeUtil.referencesThis(
NodeUtil.getFunctionBody(jArgument))) {
compiler.report(JSError.make(NodeUtil.getSourceName(n), n,
FUNCTION_LITERAL_UNDEFINED_THIS));
}
}
}
// TODO(user): Add code to TypeCheck to check that the
// types of the arguments match.
}
}
j++;
}
if (!foundTemplateTypeOfThisParameter) {
compiler.report(JSError.make(NodeUtil.getSourceName(n), n,
TEMPLATE_TYPE_OF_THIS_EXPECTED));
return;
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> propType = constraintObj.getPropertyType(prop);
if (!objType.isPropertyTypeDeclared(prop)) {
JSType typeToInfer = propType;
if (!objType.hasProperty(prop)) {
typeToInfer =
getNativeType(VOID_TYPE).getLeastSupertype(propType);
}
objType.defineInferredProperty(prop, typeToInfer, false, null);
}
}
}
}
}
/**
* If we access a property of a symbol, then that symbol is not
* null or undefined.
*/
private FlowScope dereferencePointer(Node n, FlowScope scope) {
if (n.getType() == Token.NAME) {
JSType type = getJSType(n);
JSType narrowed = type.restrictByNotNullOrUndefined();
if (type != narrowed) {
scope = scope.createChildFlowScope();
redeclare(scope, n.getString(), narrowed);
}
}
return scope;
}
private JSType getPropertyType(JSType objType, String propName,
Node n, FlowScope scope) {
// Scopes sometimes contain inferred type info about qualified names.
String qualifiedName = n.getQualifiedName();
StaticSlot<JSType> var = scope.getSlot(qualifiedName);
if (var != null) {
JSType varType = var.getType();
if (varType != null) {
if (varType.equals(getNativeType(UNKNOWN_TYPE)) &&
var != syntacticScope.getSlot(qualifiedName)) {
// If the type of this qualified name has been checked in this scope,
// then use CHECKED_UNKNOWN_TYPE instead to indicate that.
return getNativeType(CHECKED_UNKNOWN_TYPE);
} else {
return varType;
}
}
}
JSType propertyType = null;
if (objType != null) {
propertyType = objType.findPropertyType(propName);
}
if ((propertyType == null || propertyType.isUnknownType()) &&
qualifiedName != null) {
// If we find this node in the registry, then we can infer its type.
ObjectType regType = ObjectType.cast(registry.getType(qualifiedName));
if (regType != null) {
propertyType = regType.getConstructor();
}
}
return propertyType;
}
private BooleanOutcomePair traverseOr(Node n, FlowScope scope) {
return traverseShortCircuitingBinOp(n, scope, false);
}
private BooleanOutcomePair traverseShortCircuitingBinOp(
Node n, FlowScope scope, boolean condition) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
// type the left node
BooleanOutcomePair leftLiterals =
traverseWithinShortCircuitingBinOp(left,
scope.createChildFlowScope());
JSType leftType = left.getJSType();
// reverse abstract interpret the left node to produce the correct
// scope in which to verify the right node
FlowScope rightScope = reverseInterpreter.
getPreciserScopeKnowingConditionOutcome(
left, leftLiterals.getOutcomeFlowScope(left.getType(), condition),
condition);
// type
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> the right node
BooleanOutcomePair rightLiterals =
traverseWithinShortCircuitingBinOp(
right, rightScope.createChildFlowScope());
JSType rightType = right.getJSType();
JSType type;
BooleanOutcomePair literals;
if (leftType != null && rightType != null) {
leftType = leftType.getRestrictedTypeGivenToBooleanOutcome(!condition);
if (leftLiterals.toBooleanOutcomes ==
BooleanLiteralSet.get(!condition)) {
// Use the restricted left type, since the right side never gets
// evaluated.
type = leftType;
literals = leftLiterals;
} else {
// Use the join of the restricted left type knowing the outcome of the
// ToBoolean predicate and of the right type.
type = leftType.getLeastSupertype(rightType);
literals =
getBooleanOutcomePair(leftLiterals, rightLiterals, condition);
}
// Exclude the boolean type if the literal set is empty because a boolean
// can never actually be returned.
if (literals.booleanValues == BooleanLiteralSet.EMPTY &&
getNativeType(BOOLEAN_TYPE).isSubtype(type)) {
// Exclusion only make sense for a union type.
if (type instanceof UnionType) {
type = ((UnionType) type).getRestrictedUnion(
getNativeType(BOOLEAN_TYPE));
}
}
} else {
type = null;
literals = new BooleanOutcomePair(
BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH,
leftLiterals.getJoinedFlowScope(),
rightLiterals.getJoinedFlowScope());
}
n.setJSType(type);
return literals;
}
private BooleanOutcomePair traverseWithinShortCircuitingBinOp(Node n,
FlowScope scope) {
switch (n.getType()) {
case Token.AND:
return traverseAnd(n, scope);
case Token.OR:
return traverseOr(n, scope);
default:
scope = traverse(n, scope);
return newBooleanOutcomePair(n.getJSType(), scope);
}
}
/**
* Infers the boolean outcome pair that can be taken by a
* short-circuiting binary operation ({@code &&} or {@code ||}).
* @see #getBooleanOutcomes(BooleanLiteralSet, BooleanLiteralSet, boolean)
*/
BooleanOutcomePair getBooleanOutcomePair(BooleanOutcomePair left,
BooleanOutcomePair right, boolean condition) {
return new BooleanOutcomePair(
getBooleanOutcomes(left.toBooleanOutcomes, right.toBooleanOutcomes,
condition),
getBooleanOutcomes(left.booleanValues, right.booleanValues, condition),
left.getJoinedFlowScope(), right.getJoinedFlowScope());
}
/**
* Infers the boolean literal set that can be taken by a
* short-circuiting binary operation ({@code &&} or {@code ||}).
* @param left the set of possible {@code ToBoolean} predicate results for
* the expression on the left side of the operator
* @param right the set of possible {@code ToBoolean} predicate results for
* the expression on the right side of the operator
* @param condition the left side {@code ToBoolean
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>} predicate result that
* causes the right side to get evaluated (i.e. not short-circuited)
* @return a set of possible {@code ToBoolean} predicate results for the
* entire expression
*/
static BooleanLiteralSet getBooleanOutcomes(BooleanLiteralSet left,
BooleanLiteralSet right, boolean condition) {
return right.union(left.intersection(BooleanLiteralSet.get(!condition)));
}
/**
* When traversing short-circuiting binary operations, we need to keep track
* of two sets of boolean literals:
* 1. {@code toBooleanOutcomes}: boolean literals as converted from any types,
* 2. {@code booleanValues}: boolean literals from just boolean types.
*/
private final class BooleanOutcomePair {
final BooleanLiteralSet toBooleanOutcomes;
final BooleanLiteralSet booleanValues;
// The scope if only half of the expression executed, when applicable.
final FlowScope leftScope;
// The scope when the whole expression executed.
final FlowScope rightScope;
// The scope when we don't know how much of the expression is executed.
FlowScope joinedScope = null;
BooleanOutcomePair(
BooleanLiteralSet toBooleanOutcomes, BooleanLiteralSet booleanValues,
FlowScope leftScope, FlowScope rightScope) {
this.toBooleanOutcomes = toBooleanOutcomes;
this.booleanValues = booleanValues;
this.leftScope = leftScope;
this.rightScope = rightScope;
}
/**
* Gets the safe estimated scope without knowing if all of the
* subexpressions will be evaluated.
*/
FlowScope getJoinedFlowScope() {
if (joinedScope == null) {
if (leftScope == rightScope) {
joinedScope = rightScope;
} else {
joinedScope = join(leftScope, rightScope);
}
}
return joinedScope;
}
/**
* Gets the outcome scope if we do know the outcome of the entire
* expression.
*/
FlowScope getOutcomeFlowScope(int nodeType, boolean outcome) {
if (nodeType == Token.AND && outcome ||
nodeType == Token.OR && !outcome) {
// We know that the whole expression must have executed.
return rightScope;
} else {
return getJoinedFlowScope();
}
}
}
private BooleanOutcomePair newBooleanOutcomePair(
JSType jsType, FlowScope flowScope) {
if (jsType == null) {
return new BooleanOutcomePair(
BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH, flowScope, flowScope);
}
return new BooleanOutcomePair(jsType.getPossibleToBooleanOutcomes(),
registry.getNativeType(BOOLEAN_TYPE).isSubtype(jsType) ?
BooleanLiteralSet.BOTH : BooleanLiteralSet.EMPTY,
flowScope, flowScope);
}
private void redeclare(FlowScope scope, String varName, JSType varType) {
if (varType == null) {
varType = getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
if (unflowableVarNames.contains(varName)) {
return;
}
scope.inferSlotType(varName, varType);
}
/**
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> (e.g. modifying the parse
* tree).</p>
* @return whether the children of this node should be visited
*/
boolean shouldTraverse(NodeTraversal nodeTraversal, Node n, Node parent);
/**
* <p>Visits a node in post order (after its children have been visited).
* A node is visited only if all its parents should be traversed
* ({@link #shouldTraverse(NodeTraversal, Node, Node)}).</p>
* <p>Implementations can have side effects (e.g. modifying the parse
* tree).</p>
*/
void visit(NodeTraversal t, Node n, Node parent);
}
/**
* Callback that also knows about scope changes
*/
public interface ScopedCallback extends Callback {
/**
* Called immediately after entering a new scope. The new scope can
* be accessed through t.getScope()
*/
void enterScope(NodeTraversal t);
/**
* Called immediately before exiting a scope. The ending scope can
* be accessed through t.getScope()
*/
void exitScope(NodeTraversal t);
}
/**
* Abstract callback to visit all nodes in post order.
*/
public abstract static class AbstractPostOrderCallback implements Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return true;
}
}
/**
* Abstract callback to visit all nodes but not traverse into function
* bodies.
*/
public abstract static class AbstractShallowCallback implements Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
return parent == null || parent.getType() != Token.FUNCTION ||
n == parent.getFirstChild();
}
}
/**
* Abstract callback to visit all structure and statement nodes but doesn't
* traverse into functions or expressions.
*/
public abstract static class AbstractShallowStatementCallback
implements Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return parent == null || NodeUtil.isControlStructure(parent)
|| NodeUtil.isStatementBlock(parent);
}
}
/**
* Abstract callback to visit a pruned set of nodes.
*/
public abstract static class AbstractNodeTypePruningCallback
implements Callback {
private final Set<Integer> nodeTypes;
private final boolean include;
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) {
this(nodeTypes, true);
}
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include/exclude in the traversal
* @param include whether to include or exclude the nodes in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes,
boolean include) {
this.nodeTypes = nodeTypes;
this.include = include;
}
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return include == nodeTypes
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>.contains(n.getType());
}
}
/**
* Creates a node traversal using the specified callback interface.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb) {
this(compiler, cb, new SyntacticScopeCreator(compiler));
}
/**
* Creates a node traversal using the specified callback interface
* and the scope creator.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb,
ScopeCreator scopeCreator) {
this.callback = cb;
if (cb instanceof ScopedCallback) {
this.scopeCallback = (ScopedCallback) cb;
}
this.compiler = compiler;
this.sourceName = "";
this.scopeCreator = scopeCreator;
}
private void throwUnexpectedException(Exception unexpectedException) {
// If there's an unexpected exception, try to get the
// line number of the code that caused it.
String message = unexpectedException.getMessage();
// TODO(user): It is possible to get more information if curNode or
// its parent is missing. We still have the scope stack in which it is still
// very useful to find out at least which function caused the exception.
if (!sourceName.isEmpty()) {
message =
unexpectedException.getMessage() + "\n" +
formatNodeContext("Node", curNode) +
(curNode == null ?
"" :
formatNodeContext("Parent", curNode.getParent()));
}
compiler.throwInternalError(message, unexpectedException);
}
private String formatNodeContext(String label, Node n) {
if (n == null) {
return " " + label + ": NULL";
}
return " " + label + "(" + n.toString(false, false, false) + "): "
+ formatNodePosition(n);
}
/**
* Traverses a parse tree recursively.
*/
public void traverse(Node root) {
try {
sourceName = "";
curNode = root;
pushScope(root);
traverseBranch(root, null);
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
public void traverseRoots(Node ... roots) {
traverseRoots(Lists.newArrayList(roots));
}
public void traverseRoots(List<Node> roots) {
if (roots.isEmpty()) {
return;
}
try {
Node scopeRoot = roots.get(0).getParent();
Preconditions.checkState(scopeRoot != null);
sourceName = "";
curNode = scopeRoot;
pushScope(scopeRoot);
for (Node root : roots) {
Preconditions.checkState(root.getParent() == scopeRoot);
traverseBranch(root, scopeRoot);
}
popScope();
} catch (Exception unexpectedException) {
throwUnexpectedException(unexpectedException);
}
}
private static final String MISSING_SOURCE = "[source unknown]";
private String formatNodePosition(Node n) {
if (n == null) {
return MISSING_SOURCE + "\n";
}
int lineNumber = n.getLineno();
int columnNumber = n.getCharno();
String src = compiler.getSourceLine(sourceName, lineNumber);
if (src == null) {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> src = MISSING_SOURCE;
}
return sourceName + ":" + lineNumber + ":" + columnNumber + "\n"
+ src + "\n";
}
/**
* Traverses a parse tree recursively with a scope, starting with the given
* root. This should only be used in the global scope. Otherwise, use
* {@link #traverseAtScope}.
*/
void traverseWithScope(Node root, Scope s) {
Preconditions.checkState(s.isGlobal());
sourceName = "";
curNode = root;
pushScope(s);
traverseBranch(root, null);
popScope();
}
/**
* Traverses a parse tree recursively with a scope, starting at that scope's
* root.
*/
void traverseAtScope(Scope s) {
Node n = s.getRootNode();
if (n.getType() == Token.FUNCTION) {
// We need to do some extra magic to make sure that the scope doesn't
// get re-created when we dive into the function.
sourceName = getSourceName(n);
curNode = n;
pushScope(s);
Node args = n.getFirstChild().getNext();
Node body = args.getNext();
traverseBranch(args, n);
traverseBranch(body, n);
popScope();
} else {
traverseWithScope(n, s);
}
}
/**
* Traverses an inner node recursively with a refined scope. An inner node may
* be any node with a non {@code null} parent (i.e. all nodes except the
* root).
*
* @param node the node to traverse
* @param parent the node's parent, it may be not be {@code null}
* @param refinedScope the refined scope of the scope currently at the top of
* the scope stack or in trivial cases that very scope or {@code null}
*/
protected void traverseInnerNode(Node node, Node parent, Scope refinedScope) {
Preconditions.checkNotNull(parent);
if (refinedScope != null && getScope() != refinedScope) {
curNode = node;
pushScope(refinedScope);
traverseBranch(node, parent);
popScope();
} else {
traverseBranch(node, parent);
}
}
/**
* Gets the compiler.
*/
public Compiler getCompiler() {
// TODO(nicksantos): Remove this type cast. This is just temporary
// while refactoring.
return (Compiler) compiler;
}
/**
* Gets the current line number, or zero if it cannot be determined. The line
* number is retrieved lazily as a running time optimization.
*/
public int getLineNumber() {
Node cur = curNode;
while (cur != null) {
int line = cur.getLineno();
if (line >=0) {
return line;
}
cur = cur.getParent();
}
return 0;
}
/**
* Gets the current input source name.
*
* @return A string that may be empty, but not null
*/
public String getSourceName() {
return sourceName;
}
/**
* Gets the current input source.
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> */
public CompilerInput getInput() {
return compiler.getInput(sourceName);
}
/**
* Gets the current input module.
*/
public JSModule getModule() {
CompilerInput input = getInput();
return input == null ? null : input.getModule();
}
/** Returns the node currently being traversed. */
public Node getCurrentNode() {
return curNode;
}
/**
* Traverses a node recursively.
*/
public static void traverse(
AbstractCompiler compiler, Node root, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverse(root);
}
/**
* Traverses a list of node trees.
*/
public static void traverseRoots(
AbstractCompiler compiler, List<Node> roots, Callback cb) {
NodeTraversal t = new NodeTraversal(compiler, cb);
t.traverseRoots(roots);
}
/**
* Traverses a branch.
*/
@SuppressWarnings("fallthrough")
private void traverseBranch(Node n, Node parent) {
int type = n.getType();
if (type == Token.SCRIPT) {
sourceName = getSourceName(n);
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) return;
switch (type) {
case Token.FUNCTION:
traverseFunction(n, parent);
break;
default:
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
break;
}
curNode = n;
callback.visit(this, n, parent);
}
/**
* Traverses a function.
*/
private void traverseFunction(Node n, Node parent) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.getType() == Token.FUNCTION);
final Node fnName = n.getFirstChild();
boolean isFunctionExpression = (parent != null)
&& NodeUtil.isFunctionExpression(n);
if (!isFunctionExpression) {
// Functions declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (isFunctionExpression) {
// Function expression names are only accessible within the function
// scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverseBranch(args, n);
// Body
Preconditions.checkState(body.getNext() == null &&
body.getType() == Token.BLOCK);
traverseBranch(body, n);
popScope();
}
/** Examines the functions stack for the last instance of a function node. */
@SuppressWarnings("unchecked")
public Node getEnclosingFunction() {
if (scopes.size() + scopeRoots.size() < 2) {
return null;
} else {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>;
syntheticBlockEndMarker = null;
locale = null;
markAsCompiled = false;
removeTryCatchFinally = false;
closurePass = false;
rewriteNewDateGoogNow = true;
removeAbstractMethods = true;
removeClosureAsserts = false;
stripTypes = Collections.emptySet();
stripNameSuffixes = Collections.emptySet();
stripNamePrefixes = Collections.emptySet();
stripTypePrefixes = Collections.emptySet();
customPasses = null;
markNoSideEffectCalls = false;
defineReplacements = Maps.newHashMap();
tweakProcessing = TweakProcessing.OFF;
tweakReplacements = Maps.newHashMap();
moveFunctionDeclarations = false;
instrumentationTemplate = null;
appNameStr = "";
recordFunctionInformation = false;
generateExports = false;
cssRenamingMap = null;
processObjectPropertyString = false;
idGenerators = Collections.emptySet();
replaceStringsFunctionDescriptions = Collections.emptyList();
replaceStringsPlaceholderToken = "";
replaceStringsReservedStrings = Collections.emptySet();
// Output
printInputDelimiter = false;
prettyPrint = false;
lineBreak = false;
reportPath = null;
tracer = TracerMode.OFF;
colorizeErrorOutput = false;
errorFormat = ErrorFormat.SINGLELINE;
debugFunctionSideEffectsPath = null;
jsOutputFile = "";
externExports = false;
nameReferenceReportPath = null;
nameReferenceGraphPath = null;
// Debugging
aliasHandler = NULL_ALIAS_TRANSFORMATION_HANDLER;
operaCompoundAssignFix = true;
}
/**
* Returns the map of define replacements.
*/
public Map<String, Node> getDefineReplacements() {
return getReplacementsHelper(defineReplacements);
}
/**
* Returns the map of tweak replacements.
*/
public Map<String, Node> getTweakReplacements() {
return getReplacementsHelper(tweakReplacements);
}
/**
* Creates a map of String->Node from a map of String->Number/String/Boolean.
*/
private static Map<String, Node> getReplacementsHelper(
Map<String, Object> source) {
Map<String, Node> map = Maps.newHashMap();
for (Map.Entry<String, Object> entry : source.entrySet()) {
String name = entry.getKey();
Object value = entry.getValue();
if (value instanceof Boolean) {
map.put(name, ((Boolean) value).booleanValue() ?
new Node(Token.TRUE) : new Node(Token.FALSE));
} else if (value instanceof Integer) {
map.put(name, Node.newNumber(((Integer) value).intValue()));
} else if (value instanceof Double) {
map.put(name, Node.newNumber(((Double) value).doubleValue()));
} else {
Preconditions.checkState(value instanceof String);
map.put(name, Node.newString((String) value));
}
}
return map;
}
/**
* Sets the value of the {@code @define} variable in JS
* to a boolean literal.
*/
public void setDefineToBooleanLiteral(String defineName, boolean value) {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeRegistry;
import com.google.javascript.rhino.jstype.StaticScope;
import java.io.Serializable;
/**
* Represents a type expression as a miniture Rhino AST, so that the
* type expression can be evaluated later.
*
* @author nicksantos@google.com (Nick Santos)
*/
public final class JSTypeExpression implements Serializable {
private static final long serialVersionUID = 1L;
/** The root of the AST. */
private final Node root;
/** The source name where the type expression appears. */
private final String sourceName;
public JSTypeExpression(Node root, String sourceName) {
this.root = root;
this.sourceName = sourceName;
}
/**
* Make the given type expression into an optional type expression,
* if possible.
*/
public static JSTypeExpression makeOptionalArg(JSTypeExpression expr) {
if (expr.isOptionalArg() || expr.isVarArgs()) {
return expr;
} else {
return new JSTypeExpression(
new Node(Token.EQUALS, expr.root), expr.sourceName);
}
}
/**
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> * @return Whether this expression denotes an optional {@code @param}.
*/
public boolean isOptionalArg() {
return root.getType() == Token.EQUALS;
}
/**
* @return Whether this expression denotes a rest args {@code @param}.
*/
public boolean isVarArgs() {
return root.getType() == Token.ELLIPSIS;
}
/**
* Evaluates the type expression into a {@code JSType} object.
*/
public JSType evaluate(StaticScope<JSType> scope, JSTypeRegistry registry) {
return registry.createFromTypeNodes(root, sourceName, scope);
}
@Override
public boolean equals(Object other) {
return other instanceof JSTypeExpression &&
((JSTypeExpression) other).root.isEquivalentTo(root);
}
@Override
public int hashCode() {
return root.toStringTree().hashCode();
}
Node getRoot() {
return root;
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
/**
* The {@code StaticScope} interface must be implemented by any object that
* defines variables for the purposes of static analysis. It is distinguished
* from the {@code Scriptable} class that Rhino normally uses to represent a
* runtime scope.
*
* @param <T> The type of information stored about the slot
*/
public interface StaticScope<T> {
/** Returns the scope enclosing this one or null if none. */
StaticScope<T> getParentScope();
/**
* Returns any defined slot within this scope for this name. This call
* continues searching through parent scopes if a slot with this name is not
* found in the current scope.
* @param name The name of the variable slot to look up.
* @return The defined slot for the variable, or {@code null} if no
* definition exists.
*/
StaticSlot<T> getSlot(String name);
/** Like {@code getSlot} but does not recurse into parent scopes. */
StaticSlot<T> getOwnSlot(String name);
/** Returns the expected type of {@code this} in the current scope. */
T getTypeOfThis();
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> params.build();
return this;
}
/**
* Set the parameters of the function type with a specially-formatted node.
*/
public FunctionBuilder withParamsNode(Node parametersNode) {
this.parametersNode = parametersNode;
return this;
}
/** Set the return type. */
public FunctionBuilder withReturnType(JSType returnType) {
this.returnType = returnType;
return this;
}
/** Set the return type and whether it's inferred. */
public FunctionBuilder withReturnType(JSType returnType, boolean inferred) {
this.returnType = returnType;
this.inferredReturnType = inferred;
return this;
}
/** Sets an inferred return type. */
public FunctionBuilder withInferredReturnType(JSType returnType) {
this.returnType = returnType;
this.inferredReturnType = true;
return this;
}
/** Set the "this" type. */
public FunctionBuilder withTypeOfThis(ObjectType typeOfThis) {
this.typeOfThis = typeOfThis;
return this;
}
/** Set the template name. */
public FunctionBuilder withTemplateName(String templateTypeName) {
this.templateTypeName = templateTypeName;
return this;
}
/** Make this a constructor. */
public FunctionBuilder forConstructor() {
this.isConstructor = true;
return this;
}
/** Set whether this is a constructor. */
public FunctionBuilder setIsConstructor(boolean isConstructor) {
this.isConstructor = isConstructor;
return this;
}
/** Make this a native type. */
FunctionBuilder forNativeType() {
this.isNativeType = true;
return this;
}
/** Copies all the information from another function type. */
public FunctionBuilder copyFromOtherFunction(FunctionType otherType) {
this.name = otherType.getReferenceName();
this.sourceNode = otherType.getSource();
this.parametersNode = otherType.getParametersNode();
this.returnType = otherType.getReturnType();
this.typeOfThis = otherType.getTypeOfThis();
this.templateTypeName = otherType.getTemplateTypeName();
this.isConstructor = otherType.isConstructor();
this.isNativeType = otherType.isNativeObjectType();
return this;
}
/** Construct a new function type. */
public FunctionType build() {
return new FunctionType(registry, name, sourceNode,
new ArrowType(registry, parametersNode, returnType, inferredReturnType),
typeOfThis, templateTypeName, isConstructor, isNativeType);
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>Scope;
}
/** Whether this flows from a bottom scope. */
private boolean flowsFromBottom() {
return getFunctionScope().isBottom();
}
/**
* Creates an entry lattice for the flow.
*/
public static LinkedFlowScope createEntryLattice(Scope scope) {
return new LinkedFlowScope(new FlatFlowScopeCache(scope));
}
@Override
public void inferSlotType(String symbol, JSType type) {
Preconditions.checkState(!frozen);
lastSlot = new LinkedFlowSlot(symbol, type, lastSlot);
depth++;
cache.dirtySymbols.add(symbol);
}
@Override
public void inferQualifiedSlot(String symbol, JSType bottomType,
JSType inferredType) {
Scope functionScope = getFunctionScope();
if (functionScope.isLocal()) {
if (functionScope.getVar(symbol) == null && !functionScope.isBottom()) {
// When we enter a local scope, many qualified names are
// already defined even if they haven't been declared in the Scope
// object. If the name has not yet been defined in this scope, we
// need to define it now before we refine it.
functionScope.declare(symbol, null, bottomType, null);
}
inferSlotType(symbol, inferredType);
}
}
@Override
public JSType getTypeOfThis() {
return cache.functionScope.getTypeOfThis();
}
@Override
public StaticScope<JSType> getParentScope() {
return getFunctionScope().getParentScope();
}
/**
* Get the slot for the given symbol.
*/
public StaticSlot<JSType> getSlot(String name) {
if (cache.dirtySymbols.contains(name)) {
for (LinkedFlowSlot slot = lastSlot;
slot != null; slot = slot.parent) {
if (slot.getName().equals(name)) {
return slot;
}
}
}
return cache.getSlot(name);
}
@Override
public StaticSlot<JSType> getOwnSlot(String name) {
throw new UnsupportedOperationException();
}
@Override
public FlowScope createChildFlowScope() {
frozen = true;
if (depth > MAX_DEPTH) {
if (flattened == null) {
flattened = new FlatFlowScopeCache(this);
}
return new LinkedFlowScope(flattened);
}
return new LinkedFlowScope(this);
}
/**
* Iterate through all the linked flow scopes before this one.
* If there's one and only one slot defined between this scope
* and the blind scope, return it.
*/
@Override
public StaticSlot<JSType> findUniqueRefinedSlot(FlowScope blindScope) {
StaticSlot<JSType> result = null;
for (LinkedFlowScope currentScope = this;
currentScope != blindScope;
currentScope = currentScope.parent) {
for (LinkedFlowSlot currentSlot = currentScope.lastSlot;
currentSlot != null &&
(currentScope.parent == null ||
currentScope.parent.lastSlot != currentSlot);
currentSlot = currentSlot.parent) {
if (result == null) {
result = currentSlot;
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
} else if (!currentSlot.getName().equals(result.getName())) {
return null;
}
}
}
return result;
}
/**
* Look through the given scope, and try to find slots where it doesn't
* have enough type information. Then fill in that type information
* with stuff that we've inferred in the local flow.
*/
public void completeScope(Scope scope) {
for (Iterator<Var> it = scope.getVars(); it.hasNext();) {
Var var = it.next();
if (var.isTypeInferred()) {
JSType type = var.getType();
if (type == null || type.isUnknownType()) {
JSType flowType = getSlot(var.getName()).getType();
var.setType(flowType);
}
}
}
}
/**
* Remove flow scopes that add nothing to the flow.
*/
// NOTE(nicksantos): This function breaks findUniqueRefinedSlot, because
// findUniqueRefinedSlot assumes that this scope is a direct descendant
// of blindScope. This is not necessarily true if this scope has been
// optimize()d and blindScope has not. This should be fixed. For now,
// we only use optimize() where we know that we won't have to do
// a findUniqueRefinedSlot on it.
@Override
public LinkedFlowScope optimize() {
LinkedFlowScope current;
for (current = this;
current.parent != null &&
current.lastSlot == current.parent.lastSlot;
current = current.parent) {}
return current;
}
/** Join the two FlowScopes. */
static class FlowScopeJoinOp extends JoinOp.BinaryJoinOp<FlowScope> {
@SuppressWarnings("unchecked")
@Override
public FlowScope apply(FlowScope a, FlowScope b) {
// To join the two scopes, we have to
LinkedFlowScope linkedA = (LinkedFlowScope) a;
LinkedFlowScope linkedB = (LinkedFlowScope) b;
linkedA.frozen = true;
linkedB.frozen = true;
if (linkedA.optimize() == linkedB.optimize()) {
return linkedA.createChildFlowScope();
}
return new LinkedFlowScope(new FlatFlowScopeCache(linkedA, linkedB));
}
}
@Override
public boolean equals(Object other) {
if (other instanceof LinkedFlowScope) {
LinkedFlowScope that = (LinkedFlowScope) other;
if (this.optimize() == that.optimize()) {
return true;
}
// If two flow scopes are in the same function, then they could have
// two possible function scopes: the real one and the BOTTOM scope.
// If they have different function scopes, we *should* iterate thru all
// the variables in each scope and compare. However, 99.9% of the time,
// they're not equal. And the other .1% of the time, we can pretend
// they're equal--this just means that data flow analysis will have
// to propagate the entry lattice a little bit further than it
// really needs to. Everything will still come out ok.
if (this.getFunctionScope() !=
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> that.getFunctionScope()) {
return false;
}
if (cache == that.cache) {
// If the two flow scopes have the same cache, then we can check
// equality a lot faster: by just looking at the "dirty" elements
// in the cache, and comparing them in both scopes.
for (String name : cache.dirtySymbols) {
if (diffSlots(getSlot(name), that.getSlot(name))) {
return false;
}
}
return true;
}
Map<String, StaticSlot<JSType>> myFlowSlots = allFlowSlots();
Map<String, StaticSlot<JSType>> otherFlowSlots = that.allFlowSlots();
for (StaticSlot<JSType> slot : myFlowSlots.values()) {
if (diffSlots(slot, otherFlowSlots.get(slot.getName()))) {
return false;
}
otherFlowSlots.remove(slot.getName());
}
for (StaticSlot<JSType> slot : otherFlowSlots.values()) {
if (diffSlots(slot, myFlowSlots.get(slot.getName()))) {
return false;
}
}
return true;
}
return false;
}
/**
* Determines whether two slots are meaningfully different for the
* purposes of data flow analysis.
*/
private boolean diffSlots(StaticSlot<JSType> slotA,
StaticSlot<JSType> slotB) {
boolean aIsNull = slotA == null || slotA.getType() == null;
boolean bIsNull = slotB == null || slotB.getType() == null;
if (aIsNull && bIsNull) {
return false;
} else if (aIsNull ^ bIsNull) {
return true;
}
// Both slots and types must be non-null.
return slotA.getType().differsFrom(slotB.getType());
}
/**
* Gets all the symbols that have been defined before this point
* in the current flow. Does not return slots that have not changed during
* the flow.
*
* For example, consider the code:
* <code>
* var x = 3;
* function f() {
* var y = 5;
* y = 6; // FLOW POINT
* var z = y;
* return z;
* }
* </code>
* A FlowScope at FLOW POINT will return a slot for y, but not
* a slot for x or z.
*/
private Map<String, StaticSlot<JSType>> allFlowSlots() {
Map<String, StaticSlot<JSType>> slots = Maps.newHashMap();
for (LinkedFlowSlot slot = lastSlot;
slot != null; slot = slot.parent) {
if (!slots.containsKey(slot.getName())) {
slots.put(slot.getName(), slot);
}
}
for (String key : cache.symbols.keySet()) {
if (!slots.containsKey(key)) {
slots.put(key, cache.symbols.get(key));
}
}
return slots;
}
/**
* A static slot that can be used in a linked list.
*/
private static class Linked
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> joinedScopeA and joinedScopeB. Join
// the two types.
Set<String> symbolNames = Sets.newHashSet(symbols.keySet());
symbolNames.addAll(slotsB.keySet());
for (String name : symbolNames) {
StaticSlot<JSType> slotA = slotsA.get(name);
StaticSlot<JSType> slotB = slotsB.get(name);
JSType joinedType = null;
if (slotB == null || slotB.getType() == null) {
StaticSlot<JSType> fnSlot
= joinedScopeB.getFunctionScope().getSlot(name);
JSType fnSlotType = fnSlot == null ? null : fnSlot.getType();
if (fnSlotType == null) {
// Case #1 -- already inserted.
} else {
// Case #3
joinedType = slotA.getType().getLeastSupertype(fnSlotType);
}
} else if (slotA == null || slotA.getType() == null) {
StaticSlot<JSType> fnSlot
= joinedScopeA.getFunctionScope().getSlot(name);
JSType fnSlotType = fnSlot == null ? null : fnSlot.getType();
if (fnSlotType == null) {
// Case #2
symbols.put(name, slotB);
} else {
// Case #4
joinedType = slotB.getType().getLeastSupertype(fnSlotType);
}
} else {
// Case #5
joinedType =
slotA.getType().getLeastSupertype(slotB.getType());
}
if (joinedType != null) {
symbols.put(name, new SimpleSlot(name, joinedType, true));
}
}
}
/**
* Get the slot for the given symbol.
*/
public StaticSlot<JSType> getSlot(String name) {
if (symbols.containsKey(name)) {
return symbols.get(name);
} else {
return functionScope.getSlot(name);
}
}
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> synthesizedExternsRoot = null;
// Vars that still need to be declared in externs. These will be declared
// at the end of the pass, or when we see the equivalent var declared
// in the normal code.
private final Set<String> varsToDeclareInExterns = Sets.newHashSet();
private final AbstractCompiler compiler;
// Whether this is the post-processing sanity check.
private final boolean sanityCheck;
// Whether extern checks emit error.
private final boolean strictExternCheck;
VarCheck(AbstractCompiler compiler) {
this(compiler, false);
}
VarCheck(AbstractCompiler compiler, boolean sanityCheck) {
this.compiler = compiler;
this.strictExternCheck = compiler.getErrorLevel(
JSError.make("", 0, 0, UNDEFINED_EXTERN_VAR_ERROR)) == CheckLevel.ERROR;
this.sanityCheck = sanityCheck;
}
@Override
public void process(Node externs, Node root) {
// Don't run externs-checking in sanity check mode. Normalization will
// remove duplicate VAR declarations, which will make
// externs look like they have assigns.
if (!sanityCheck) {
NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck());
}
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
for (String varName : varsToDeclareInExterns) {
createSynthesizedExternVar(varName);
}
}
@Override
public void hotSwapScript(Node scriptRoot) {
Preconditions.checkState(scriptRoot.getType() == Token.SCRIPT);
NodeTraversal t = new NodeTraversal(compiler, this);
// Note we use the global scope to prevent wrong "undefined-var errors" on
// variables that are defined in other js files.
t.traverseWithScope(scriptRoot,
SyntacticScopeCreator.generateUntypedTopScope(compiler));
// TODO(bashir) Check if we need to createSynthesizedExternVar like process.
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() != Token.NAME) {
return;
}
String varName = n.getString();
// Only a function can have an empty name.
if (varName.isEmpty()) {
Preconditions.checkState(NodeUtil.isFunction(parent));
// A function declaration with an empty name passes Rhino,
// but is supposed to be a syntax error according to the spec.
if (!NodeUtil.isFunctionExpression(parent)) {
t.report(n, INVALID_FUNCTION_DECL);
}
return;
}
// Check if this is a declaration for a var that has been declared
// elsewhere. If so, mark it as a duplicate.
if ((parent.getType() == Token.VAR ||
NodeUtil.isFunctionDeclaration(parent)) &&
varsToDeclareInExterns.contains(varName)) {
createSynthesizedExternVar(varName);
n.addSuppression("duplicate");
}
// Check that the var has been declared.
Scope scope = t.getScope();
Scope.Var var = scope
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>.getVar(varName);
if (var == null) {
if (NodeUtil.isFunctionExpression(parent)) {
// e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the
// current scope.
} else {
// The extern checks are stricter, don't report a second error.
if (!strictExternCheck || !t.getInput().isExtern()) {
t.report(n, UNDEFINED_VAR_ERROR, varName);
}
if (sanityCheck) {
throw new IllegalStateException("Unexpected variable " + varName);
} else {
createSynthesizedExternVar(varName);
scope.getGlobalScope().declare(varName, n,
null, getSynthesizedExternsInput());
}
}
return;
}
CompilerInput currInput = t.getInput();
CompilerInput varInput = var.input;
if (currInput == varInput || currInput == null || varInput == null) {
// The variable was defined in the same file. This is fine.
return;
}
// Check module dependencies.
JSModule currModule = currInput.getModule();
JSModule varModule = varInput.getModule();
JSModuleGraph moduleGraph = compiler.getModuleGraph();
if (varModule != currModule && varModule != null && currModule != null) {
if (moduleGraph.dependsOn(currModule, varModule)) {
// The module dependency was properly declared.
} else {
if (!sanityCheck && scope.isGlobal()) {
if (moduleGraph.dependsOn(varModule, currModule)) {
// The variable reference violates a declared module dependency.
t.report(n, VIOLATED_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
} else {
// The variable reference is between two modules that have no
// dependency relationship. This should probably be considered an
// error, but just issue a warning for now.
t.report(n, MISSING_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
} else {
t.report(n, STRICT_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
}
}
}
/**
* Create a new variable in a synthetic script. This will prevent
* subsequent compiler passes from crashing.
*/
private void createSynthesizedExternVar(String varName) {
Node nameNode = Node.newString(Token.NAME, varName);
// Mark the variable as constant if it matches the coding convention
// for constant vars.
// NOTE(nicksantos): honestly, i'm not sure how much this matters.
// AFAIK, all people who use the CONST coding convention also
// compile with undeclaredVars as errors. We have some test
// cases for this configuration though, and it makes them happier.
if (compiler.getCodingConvention().isConstant(varName)) {
nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
getSynthesizedExternsRoot().addChildToBack(
new Node(Token.
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>VAR, nameNode));
varsToDeclareInExterns.remove(varName);
compiler.reportCodeChange();
}
/**
* A check for name references in the externs inputs. These used to prevent
* a variable from getting renamed, but no longer have any effect.
*/
private class NameRefInExternsCheck extends AbstractPostOrderCallback {
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.NAME) {
switch (parent.getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.LP:
// These are okay.
break;
case Token.GETPROP:
if (n == parent.getFirstChild()) {
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
t.report(n, UNDEFINED_EXTERN_VAR_ERROR, n.getString());
varsToDeclareInExterns.add(n.getString());
}
}
break;
default:
t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString());
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
varsToDeclareInExterns.add(n.getString());
}
break;
}
}
}
}
/** Lazily create a "new" externs input for undeclared variables. */
private CompilerInput getSynthesizedExternsInput() {
if (synthesizedExternsInput == null) {
synthesizedExternsInput =
compiler.newExternInput(SYNTHETIC_VARS_DECLAR);
}
return synthesizedExternsInput;
}
/** Lazily create a "new" externs root for undeclared variables. */
private Node getSynthesizedExternsRoot() {
if (synthesizedExternsRoot == null) {
CompilerInput synthesizedExterns = getSynthesizedExternsInput();
synthesizedExternsRoot = synthesizedExterns.getAstRoot(compiler);
}
return synthesizedExternsRoot;
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> compiler, Behavior behavior,
Predicate<Var> varFilter) {
this.compiler = compiler;
this.behavior = behavior;
this.varFilter = varFilter;
}
/**
* Convenience method for running this pass over a tree with this
* class as a callback.
*/
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
/**
* Same as process but only runs on a part of AST associated to one script.
*/
@Override
public void hotSwapScript(Node scriptRoot) {
NodeTraversal.traverse(compiler, scriptRoot, this);
}
/**
* Gets the variables that were referenced in this callback.
*/
public Set<Var> getReferencedVariables() {
return referenceMap.keySet();
}
/**
* Gets the reference collection for the given variable.
*/
public ReferenceCollection getReferenceCollection(Var v) {
return referenceMap.get(v);
}
/**
* For each node, update the block stack and reference collection
* as appropriate.
*/
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.NAME) {
Var v;
if (n.getString().equals("arguments")) {
v = t.getScope().getArgumentsVar();
} else {
v = t.getScope().getVar(n.getString());
}
if (v != null && varFilter.apply(v)) {
addReference(t, v,
new Reference(n, parent, t, blockStack.peek()));
}
}
if (isBlockBoundary(n, parent)) {
blockStack.pop();
}
}
/**
* Updates block stack and invokes any additional behavior.
*/
public void enterScope(NodeTraversal t) {
Node n = t.getScope().getRootNode();
BasicBlock parent = blockStack.isEmpty() ? null : blockStack.peek();
blockStack.push(new BasicBlock(parent, n));
}
/**
* Updates block statck and invokes any additional behavior.
*/
public void exitScope(NodeTraversal t) {
blockStack.pop();
if (t.getScope().isGlobal()) {
// Update global scope reference lists when we are done with it.
compiler.updateGlobalVarReferences(referenceMap, t.getScopeRoot());
behavior.afterExitScope(t, compiler.getGlobalVarReferences());
} else {
behavior.afterExitScope(t, new ReferenceMapWrapper(referenceMap));
}
}
/**
* Updates block stack.
*/
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// If node is a new basic block, put on basic block stack
if (isBlockBoundary(n, parent)) {
blockStack.push(new BasicBlock(blockStack.peek(), n));
}
return true;
}
/**
* @return true if this node marks the start of a new basic block
*/
private static boolean isBlockBoundary(Node n, Node parent) {
if (parent != null) {
switch (parent.getType()) {
case Token.DO
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>:
case Token.FOR:
case Token.TRY:
case Token.WHILE:
case Token.WITH:
// NOTE: TRY has up to 3 child blocks:
// TRY
// BLOCK
// BLOCK
// CATCH
// BLOCK
// Note that there is an explcit CATCH token but no explicit
// FINALLY token. For simplicity, we consider each BLOCK
// a separate basic BLOCK.
return true;
case Token.AND:
case Token.HOOK:
case Token.IF:
case Token.OR:
// The first child of a conditional is not a boundary,
// but all the rest of the children are.
return n != parent.getFirstChild();
}
}
return n.getType() == Token.CASE;
}
private void addReference(NodeTraversal t, Var v, Reference reference) {
// Create collection if none already
ReferenceCollection referenceInfo = referenceMap.get(v);
if (referenceInfo == null) {
referenceInfo = new ReferenceCollection();
referenceMap.put(v, referenceInfo);
}
// Add this particular reference
referenceInfo.add(reference, t, v);
}
interface ReferenceMap {
ReferenceCollection getReferences(Var var);
}
private static class ReferenceMapWrapper implements ReferenceMap {
private final Map<Var, ReferenceCollection> referenceMap;
public ReferenceMapWrapper(Map<Var, ReferenceCollection> referenceMap) {
this.referenceMap = referenceMap;
}
@Override
public ReferenceCollection getReferences(Var var) {
return referenceMap.get(var);
}
}
/**
* Way for callers to add specific behavior during traversal that
* utilizes the built-up reference information.
*/
interface Behavior {
/**
* Called after we finish with a scope.
*/
void afterExitScope(NodeTraversal t, ReferenceMap referenceMap);
}
static Behavior DO_NOTHING_BEHAVIOR = new Behavior() {
@Override
public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {}
};
/**
* A collection of references. Can be subclassed to apply checks or
* store additional state when adding.
*/
static class ReferenceCollection {
List<Reference> references = Lists.newArrayList();
void add(Reference reference, NodeTraversal t, Var v) {
references.add(reference);
}
/**
* Determines if the variable for this reference collection is
* "well-defined." A variable is well-defined if we can prove at
* compile-time that it's assigned a value before it's used.
*
* Notice that if this function returns false, this doesn't imply that the
* variable is used before it's assigned. It just means that we don't
* have enough information to make a definitive judgement.
*/
protected boolean isWellDefined() {
int size = references.size();
if (size == 0) {
return false;
}
// If this is a declaration that does not instantiate the variable,
// it's not well-defined.
Reference init = getInitializingReference();
if (init == null) {
return false;
}
Preconditions.checkState(references.get
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> is not in a loop.
for (BasicBlock block = ref.getBasicBlock();
block != null; block = block.getParent()) {
if (block.isFunction) {
break;
} else if (block.isLoop) {
return false;
}
}
return true;
}
/**
* @return The one and only assignment. Returns if there are 0 or 2+
* assignments.
*/
private Reference getOneAndOnlyAssignment() {
Reference assignment = null;
int size = references.size();
for (int i = 0; i < size; i++) {
Reference ref = references.get(i);
if (ref.isLvalue() || ref.isInitializingDeclaration()) {
if (assignment == null) {
assignment = ref;
} else {
return null;
}
}
}
return assignment;
}
/**
* @return Whether the variable is never assigned a value.
*/
boolean isNeverAssigned() {
int size = references.size();
for (int i = 0; i < size; i++) {
Reference ref = references.get(i);
if (ref.isLvalue() || ref.isInitializingDeclaration()) {
return false;
}
}
return true;
}
boolean firstReferenceIsAssigningDeclaration() {
int size = references.size();
if (size > 0 && references.get(0).isInitializingDeclaration()) {
return true;
}
return false;
}
}
/**
* Represents a single declaration or reference to a variable.
*/
static final class Reference {
private static final Set<Integer> DECLARATION_PARENTS =
ImmutableSet.of(Token.VAR, Token.FUNCTION, Token.CATCH);
private final Node nameNode;
private final Node parent;
private final Node grandparent;
private final BasicBlock basicBlock;
private final Scope scope;
private final String sourceName;
Reference(Node nameNode, Node parent, NodeTraversal t,
BasicBlock basicBlock) {
this(nameNode, parent, parent.getParent(), basicBlock, t.getScope(),
t.getSourceName());
}
// Bleeding functions are weird, because the declaration does
// not appear inside their scope. So they need their own constructor.
static Reference newBleedingFunction(NodeTraversal t,
BasicBlock basicBlock, Node func) {
return new Reference(func.getFirstChild(), func, func.getParent(),
basicBlock, t.getScope(), t.getSourceName());
}
private Reference(Node nameNode, Node parent, Node grandparent,
BasicBlock basicBlock, Scope scope, String sourceName) {
this.nameNode = nameNode;
this.parent = parent;
this.grandparent = grandparent;
this.basicBlock = basicBlock;
this.scope = scope;
this.sourceName = sourceName;
}
boolean isDeclaration() {
return DECLARATION_PARENTS.contains(parent.getType()) ||
parent.getType() == Token.LP &&
grandparent.getType() == Token.FUNCTION;
}
boolean isVarDeclaration() {
return parent.getType() == Token.VAR;
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
}
boolean isHoistedFunction() {
return NodeUtil.isHoistedFunctionDeclaration(parent);
}
/**
* Determines whether the variable is initialized at the declaration.
*/
boolean isInitializingDeclaration() {
// VAR is the only type of variable declaration that may not initialize
// its variable. Catch blocks, named functions, and parameters all do.
return isDeclaration() &&
(parent.getType() != Token.VAR || nameNode.getFirstChild() != null);
}
/**
* @return For an assignment, variable declaration, or function declaration
* return the assigned value, otherwise null.
*/
Node getAssignedValue() {
return (parent.getType() == Token.FUNCTION)
? parent : NodeUtil.getAssignedValue(getNameNode());
}
BasicBlock getBasicBlock() {
return basicBlock;
}
Node getParent() {
return parent;
}
Node getNameNode() {
return nameNode;
}
Node getGrandparent() {
return grandparent;
}
private static boolean isLhsOfForInExpression(Node n) {
Node parent = n.getParent();
if (parent.getType() == Token.VAR) {
return isLhsOfForInExpression(parent);
}
return NodeUtil.isForIn(parent) && parent.getFirstChild() == n;
}
boolean isSimpleAssignmentToName() {
return parent.getType() == Token.ASSIGN
&& parent.getFirstChild() == nameNode;
}
boolean isLvalue() {
int parentType = parent.getType();
return (parentType == Token.VAR && nameNode.getFirstChild() != null)
|| parentType == Token.INC
|| parentType == Token.DEC
|| (NodeUtil.isAssignmentOp(parent)
&& parent.getFirstChild() == nameNode)
|| isLhsOfForInExpression(nameNode);
}
Scope getScope() {
return scope;
}
public String getSourceName() {
return sourceName;
}
}
/**
* Represents a section of code that is uninterrupted by control structures
* (conditional or iterative logic).
*/
static final class BasicBlock {
private final BasicBlock parent;
/**
* Determines whether the block may not be part of the normal control flow,
* but instead "hoisted" to the top of the scope.
*/
private final boolean isHoisted;
/**
* Whether this block denotes a function scope.
*/
private final boolean isFunction;
/**
* Whether this block denotes a loop.
*/
private final boolean isLoop;
/**
* Creates a new block.
* @param parent The containing block.
* @param root The root node of the block.
*/
BasicBlock(BasicBlock parent, Node root) {
this.parent = parent;
// only named functions may be hoisted.
this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root);
this.isFunction = root.getType() == Token.FUNCTION;
if (root.getParent() != null) {
int pType = root.getParent().getType();
this.isLoop = pType == Token.DO ||
pType == Token.WHILE ||
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
pType == Token.FOR;
} else {
this.isLoop = false;
}
}
BasicBlock getParent() {
return parent;
}
/**
* Determines whether this block is equivalent to the very first block that
* is created when reference collection traversal enters global scope. Note
* that when traversing a single script in a hot-swap fashion a new instance
* of {@code BasicBlock} is created.
*
* @return true if this is global scope block.
*/
boolean isGlobalScopeBlock() {
return getParent() == null;
}
/**
* Determines whether this block is guaranteed to begin executing before
* the given block does.
*/
boolean provablyExecutesBefore(BasicBlock thatBlock) {
// If thatBlock is a descendant of this block, and there are no hoisted
// blocks between them, then this block must start before thatBlock.
BasicBlock currentBlock;
for (currentBlock = thatBlock;
currentBlock != null && currentBlock != this;
currentBlock = currentBlock.getParent()) {
if (currentBlock.isHoisted) {
return false;
}
}
if (currentBlock == this) {
return true;
}
if (isGlobalScopeBlock() && thatBlock.isGlobalScopeBlock()) {
return true;
}
return false;
}
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
ChainableReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
Preconditions.checkNotNull(convention);
this.convention = convention;
this.typeRegistry = typeRegistry;
firstLink = this;
nextLink = null;
}
/**
* Appends a link to {@code this}, returning the updated last link.
* <p>
* The pattern {@code new X().append(new Y())...append(new Z())} forms a
* chain starting with X, then Y, then ... Z.
* @param lastLink a chainable interpreter, with no next link
* @return the updated last link
*/
ChainableReverseAbstractInterpreter append(
ChainableReverseAbstractInterpreter lastLink) {
Preconditions.checkArgument(lastLink.nextLink == null);
this.nextLink = lastLink;
lastLink.firstLink = this.firstLink;
return lastLink;
}
/**
* Gets the first link of this chain.
*/
ChainableReverseAbstractInterpreter getFirst() {
return firstLink;
}
/**
* Calculates the preciser scope starting with the first link.
*/
protected FlowScope firstPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return firstLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
/**
* Delegates the calculation of the preciser scope to the next link.
* If there is no next link, returns the blind scope.
*/
protected FlowScope nextPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return nextLink != null ? nextLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome) : blindScope;
}
/**
* Returns the type of a node in the given scope if the node corresponds to a
* name whose type is capable of being refined.
* @return The current type of the node if it can be refined, null otherwise.
*/
JSType getTypeIfRefinable(Node node, FlowScope scope) {
switch (node.getType()) {
case Token.NAME:
StaticSlot<JSType> nameVar = scope.getSlot(node.getString());
if (nameVar != null) {
JSType nameVarType = nameVar.getType();
if (nameVarType == null) {
nameVarType = node.getJSType();
}
return nameVarType;
}
return null;
case Token.GETPROP:
String qualifiedName = node.getQualifiedName();
if (qualifiedName == null) {
return null;
}
StaticSlot<JSType> propVar = scope.getSlot(qualifiedName);
JSType propVarType = null;
if (propVar != null) {
propVarType = propVar.getType();
}
if (propVarType == null) {
propVarType = node.getJSType();
}
if (propVarType == null) {
propVarType = getNativeType(UNKNOWN_TYPE);
}
return propVarType;
}
return null;
}
/**
* Declares a ref
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>ined type in {@code scope} for the name represented by
* {@code node}. It must be possible to refine the type of the given node in
* the given scope, as determined by {@link #getTypeIfRefinable}.
*/
protected void declareNameInScope(FlowScope scope, Node node, JSType type) {
switch (node.getType()) {
case Token.NAME:
scope.inferSlotType(node.getString(), type);
break;
case Token.GETPROP:
String qualifiedName = node.getQualifiedName();
Preconditions.checkNotNull(qualifiedName);
JSType origType = node.getJSType();
origType = origType == null ? getNativeType(UNKNOWN_TYPE) : origType;
scope.inferQualifiedSlot(qualifiedName, origType, type);
break;
default:
throw new IllegalArgumentException("Node cannot be refined. \n" +
node.toStringTree());
}
}
/**
* @see #getRestrictedWithoutUndefined(JSType)
*/
private final Visitor<JSType> restrictUndefinedVisitor =
new Visitor<JSType>() {
public JSType caseEnumElementType(EnumElementType enumElementType) {
JSType type = enumElementType.getPrimitiveType().visit(this);
if (type != null && enumElementType.getPrimitiveType().equals(type)) {
return enumElementType;
} else {
return type;
}
}
public JSType caseAllType() {
return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE,
STRING_TYPE, BOOLEAN_TYPE, NULL_TYPE);
}
public JSType caseNoObjectType() {
return getNativeType(NO_OBJECT_TYPE);
}
public JSType caseNoType() {
return getNativeType(NO_TYPE);
}
public JSType caseBooleanType() {
return getNativeType(BOOLEAN_TYPE);
}
public JSType caseFunctionType(FunctionType type) {
return type;
}
public JSType caseNullType() {
return getNativeType(NULL_TYPE);
}
public JSType caseNumberType() {
return getNativeType(NUMBER_TYPE);
}
public JSType caseObjectType(ObjectType type) {
return type;
}
public JSType caseStringType() {
return getNativeType(STRING_TYPE);
}
public JSType caseUnionType(UnionType type) {
return type.getRestrictedUnion(getNativeType(VOID_TYPE));
}
public JSType caseUnknownType() {
return getNativeType(UNKNOWN_TYPE);
}
public JSType caseVoidType() {
return null;
}
};
/**
* @see #getRestrictedWithoutNull(JSType)
*/
private final Visitor<JSType> restrictNullVisitor =
new Visitor<JSType>() {
public JSType caseEnumElementType(EnumElementType enumElementType) {
JSType type = enumElementType.getPrimitiveType().visit(this);
if (type != null && enumElementType.getPrimitiveType().equals(type)) {
return enumElementType;
} else {
return type;
}
}
public JSType caseAllType() {
return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.regex.RegExpTree;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.Node;
/**
* Look for references to the global RegExp object that would cause
* regular expressions to be unoptimizable, and checks that regular expressions
* are syntactically valid.
*
* @author johnlenz@google.com (John Lenz)
*/
class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass {
static final DiagnosticType REGEXP_REFERENCE =
DiagnosticType.warning("JSC_REGEXP_REFERENCE",
"References to the global RegExp object prevents " +
"optimization of regular expressions.");
static final DiagnosticType MALFORMED_REGEXP = DiagnosticType.warning(
"JSC_MALFORMED_REGEXP",
"Malformed Regular Expression: {0}");
private final AbstractCompiler compiler;
private boolean globalRegExpPropertiesUsed = false;
public boolean isGlobalRegExpPropertiesUsed() {
return globalRegExpPropertiesUsed;
}
public CheckRegExp(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isReferenceName(n)) {
String name = n.getString();
if (name.equals("RegExp") && t.getScope().getVar(name) == null) {
int parentType = parent.getType();
boolean first = (n == parent.getFirstChild());
if (!((parentType == Token.NEW && first)
|| (parentType == Token.CALL && first)
|| (parentType == Token.INSTANCEOF && !first))) {
t.report(n, REGEXP_REFERENCE);
globalRegExpPropertiesUsed = true;
}
}
// Check the syntax of regular expression patterns.
} else if (n.getType() == Token.REGEXP) {
String pattern = n.getFirstChild().getString();
String flags = n.getChildCount() == 2
? n.getLastChild().getString() : "";
try {
RegExpTree.parseRegExp(pattern, flags);
} catch (IllegalArgumentException ex) {
t.report(n, MALFORMED_REGEXP, ex.getMessage());
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> // From Section 7.6.1.2
"class", "const", "enum", "export", "extends", "import", "super",
"implements", "interface", "let", "package", "private", "protected",
"public", "static", "yield");
private final Set<String> reservedKeywords;
// @license text gets appended onto the fileLevelJsDocBuilder as found,
// and stored in JSDocInfo for placeholder node.
Node rootNodeJsDocHolder = new Node(Token.SCRIPT);
Node.FileLevelJsDocBuilder fileLevelJsDocBuilder =
rootNodeJsDocHolder.getJsDocBuilderForNode();
JSDocInfo fileOverviewInfo = null;
// Use a template node for properties set on all nodes to minimize the
// memory footprint associated with these.
private Node templateNode;
// TODO(johnlenz): Consider creating a template pool for ORIGINALNAME_PROP.
private IRFactory(String sourceString,
String sourceName,
Config config,
ErrorReporter errorReporter) {
this.sourceString = sourceString;
this.sourceName = sourceName;
this.config = config;
this.errorReporter = errorReporter;
this.transformDispatcher = new TransformDispatcher();
// The template node properties are applied to all nodes in this transform.
this.templateNode = createTemplateNode();
switch (config.languageMode) {
case ECMASCRIPT3:
// Reserved words are handled by the Rhino parser.
reservedKeywords = null;
break;
case ECMASCRIPT5:
reservedKeywords = ES5_RESERVED_KEYWORDS;
break;
case ECMASCRIPT5_STRICT:
reservedKeywords = ES5_STRICT_RESERVED_KEYWORDS;
break;
default:
throw new IllegalStateException("unknown language mode");
}
}
// Create a template node to use as a source of common attributes, this allows
// the prop structure to be shared among all the node from this source file.
// This reduces the cost of these properties to O(nodes) to O(files).
private Node createTemplateNode() {
// The Node type choice is arbitrary.
Node templateNode = new Node(Token.SCRIPT);
templateNode.putProp(Node.SOURCENAME_PROP, sourceName);
return templateNode;
}
public static Node transformTree(AstRoot node,
String sourceString,
Config config,
ErrorReporter errorReporter) {
IRFactory irFactory = new IRFactory(sourceString, node.getSourceName(),
config, errorReporter);
Node irNode = irFactory.transform(node);
if (node.getComments() != null) {
for (Comment comment : node.getComments()) {
if (comment.getCommentType() == CommentType.JSDOC &&
!comment.isParsed()) {
irFactory.handlePossibleFileOverviewJsDoc(comment);
} else if (comment.getCommentType() == CommentType.BLOCK) {
irFactory.handleBlockComment(comment);
}
}
}
irFactory.setFileOverviewJsDoc(irNode);
return irNode;
}
private void setFileOverviewJsDoc(Node irNode) {
//
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> Only after we've seen all @fileoverview entries, attach the
// last one to the root node, and copy the found license strings
// to that node.
irNode.setJSDocInfo(rootNodeJsDocHolder.getJSDocInfo());
if (fileOverviewInfo != null) {
if ((irNode.getJSDocInfo() != null) &&
(irNode.getJSDocInfo().getLicense() != null)) {
fileOverviewInfo.setLicense(irNode.getJSDocInfo().getLicense());
}
irNode.setJSDocInfo(fileOverviewInfo);
}
}
private Node transformBlock(AstNode node) {
Node irNode = transform(node);
if (irNode.getType() != Token.BLOCK) {
if (irNode.getType() == Token.EMPTY) {
irNode.setType(Token.BLOCK);
irNode.setWasEmptyNode(true);
} else {
Node newBlock = newNode(Token.BLOCK, irNode);
newBlock.setLineno(irNode.getLineno());
newBlock.setCharno(irNode.getCharno());
irNode = newBlock;
}
}
return irNode;
}
/**
* Check to see if the given block comment looks like it should be JSDoc.
*/
private void handleBlockComment(Comment comment) {
String value = comment.getValue();
if (value.indexOf("/* @") != -1 ||
value.indexOf("\n * @") != -1) {
errorReporter.warning(
SUSPICIOUS_COMMENT_WARNING,
sourceName,
comment.getLineno(), "", 0);
}
}
/**
* @return true if the jsDocParser represents a fileoverview.
*/
private boolean handlePossibleFileOverviewJsDoc(
JsDocInfoParser jsDocParser) {
if (jsDocParser.getFileOverviewJSDocInfo() != fileOverviewInfo) {
fileOverviewInfo = jsDocParser.getFileOverviewJSDocInfo();
return true;
}
return false;
}
private void handlePossibleFileOverviewJsDoc(Comment comment) {
JsDocInfoParser jsDocParser = createJsDocInfoParser(comment);
comment.setParsed(true);
handlePossibleFileOverviewJsDoc(jsDocParser);
}
private JSDocInfo handleJsDoc(AstNode node) {
Comment comment = node.getJsDocNode();
if (comment != null) {
JsDocInfoParser jsDocParser = createJsDocInfoParser(comment);
comment.setParsed(true);
if (!handlePossibleFileOverviewJsDoc(jsDocParser)) {
return jsDocParser.retrieveAndResetParsedJSDocInfo();
}
}
return null;
}
private Node transform(AstNode node) {
JSDocInfo jsDocInfo = handleJsDoc(node);
Node irNode = justTransform(node);
if (jsDocInfo != null) {
irNode.setJSDocInfo(jsDocInfo);
}
setSourceInfo(irNode, node);
return irNode;
}
private Node transformNameAsString(Name node) {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
JSDocInfo jsDocInfo = handleJsDoc(node);
Node irNode = transformDispatcher.processName(node, true);
if (jsDocInfo != null) {
irNode.setJSDocInfo(jsDocInfo);
}
setSourceInfo(irNode, node);
return irNode;
}
private Node transformNumberAsString(NumberLiteral literalNode) {
JSDocInfo jsDocInfo = handleJsDoc(literalNode);
Node irNode = newStringNode(getStringValue(literalNode.getNumber()));
if (jsDocInfo != null) {
irNode.setJSDocInfo(jsDocInfo);
}
setSourceInfo(irNode, literalNode);
return irNode;
}
private static String getStringValue(double value) {
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString(value);
}
}
private void setSourceInfo(Node irNode, AstNode node) {
// If we have a named function, set the position to that of the name.
if (irNode.getType() == Token.FUNCTION &&
irNode.getFirstChild().getLineno() != -1) {
irNode.setLineno(irNode.getFirstChild().getLineno());
irNode.setCharno(irNode.getFirstChild().getCharno());
} else {
if (irNode.getLineno() == -1) {
// If we didn't already set the line, then set it now. This avoids
// cases like ParenthesizedExpression where we just return a previous
// node, but don't want the new node to get its parent's line number.
int lineno = node.getLineno();
irNode.setLineno(lineno);
int charno = position2charno(node.getAbsolutePosition());
irNode.setCharno(charno);
}
}
}
/**
* Creates a JsDocInfoParser and parses the JsDoc string.
*
* Used both for handling individual JSDoc comments and for handling
* file-level JSDoc comments (@fileoverview and @license).
*
* @param node The JsDoc Comment node to parse.
* @return A JSDocInfoParser. Will contain either fileoverview jsdoc, or
* normal jsdoc, or no jsdoc (if the method parses to the wrong level).
*/
private JsDocInfoParser createJsDocInfoParser(Comment node) {
String comment = node.getValue();
int lineno = node.getLineno();
int position = node.getAbsolutePosition();
// The JsDocInfoParser expects the comment without the initial '/**'.
int numOpeningChars = 3;
JsDocInfoParser jsdocParser =
new JsDocInfoParser(
new JsDocTokenStream(comment.substring(numOpeningChars),
lineno,
position2charno(position) + numOpeningChars),
node,
sourceName,
config,
errorReporter);
jsdocParser.setFileLevelJsDocBuilder(fileLevelJsDocBuilder);
js
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>docParser.setFileOverviewJSDocInfo(fileOverviewInfo);
jsdocParser.parse();
return jsdocParser;
}
private int position2charno(int position) {
int lineIndex = sourceString.lastIndexOf('\n', position);
if (lineIndex == -1) {
return position;
} else {
// Subtract one for initial position being 0.
return position - lineIndex - 1;
}
}
private Node justTransform(AstNode node) {
return transformDispatcher.process(node);
}
private class TransformDispatcher extends TypeSafeDispatcher<Node> {
private Node processGeneric(
com.google.javascript.jscomp.mozilla.rhino.Node n) {
Node node = newNode(transformTokenType(n.getType()));
for (com.google.javascript.jscomp.mozilla.rhino.Node child : n) {
node.addChildToBack(transform((AstNode)child));
}
return node;
}
/**
* Transforms the given node and then sets its type to Token.STRING if it
* was Token.NAME. If its type was already Token.STRING, then quotes it.
* Used for properties, as the old AST uses String tokens, while the new one
* uses Name tokens for unquoted strings. For example, in
* var o = {'a' : 1, b: 2};
* the string 'a' is quoted, while the name b is turned into a string, but
* unquoted.
*/
private Node transformAsString(AstNode n) {
Node ret;
if (n instanceof Name) {
ret = transformNameAsString((Name)n);
} else if (n instanceof NumberLiteral) {
ret = transformNumberAsString((NumberLiteral)n);
ret.putBooleanProp(Node.QUOTED_PROP, true);
} else {
ret = transform(n);
ret.putBooleanProp(Node.QUOTED_PROP, true);
}
Preconditions.checkState(ret.getType() == Token.STRING);
return ret;
}
@Override
Node processArrayLiteral(ArrayLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.ARRAYLIT);
for (AstNode child : literalNode.getElements()) {
Node c = transform(child);
node.addChildToBack(c);
}
return node;
}
@Override
Node processAssignment(Assignment assignmentNode) {
Node assign = processInfixExpression(assignmentNode);
Node target = assign.getFirstChild();
if (!validAssignmentTarget(target)) {
errorReporter.error(
"invalid assignment target",
sourceName,
target.getLineno(), "", 0);
}
return assign;
}
@Override
Node processAstRoot(AstRoot rootNode) {
Node node = newNode(Token.SCRIPT);
for (com.google.javascript.jscomp.mozilla.rhino.Node child : rootNode) {
node.addChildToBack(transform((AstNode)child));
}
parseDirectives(node);
return node;
}
/**
* Parse the
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> directives, encode them in the AST, and remove their nodes.
*
* For information on ES5 directives, see section 14.1 of
* Ecma-262, Edition 5.
*
* It would be nice if Rhino would eventually take care of this for
* us, but right now their directive-processing is a one-off.
*/
private void parseDirectives(Node node) {
// Remove all the directives, and encode them in the AST.
Set<String> directives = null;
while (isDirective(node.getFirstChild())) {
String directive = node.removeFirstChild().getFirstChild().getString();
if (directives == null) {
directives = Sets.newHashSet(directive);
} else {
directives.add(directive);
}
}
if (directives != null) {
node.setDirectives(directives);
}
}
private boolean isDirective(Node n) {
if (n == null) return false;
int nType = n.getType();
return (nType == Token.EXPR_RESULT || nType == Token.EXPR_VOID) &&
n.getFirstChild().getType() == Token.STRING &&
ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString());
}
@Override
Node processBlock(Block blockNode) {
return processGeneric(blockNode);
}
@Override
Node processBreakStatement(BreakStatement statementNode) {
Node node = newNode(Token.BREAK);
if (statementNode.getBreakLabel() != null) {
Node labelName = transform(statementNode.getBreakLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processCatchClause(CatchClause clauseNode) {
AstNode catchVar = clauseNode.getVarName();
Node node = newNode(Token.CATCH, transform(catchVar));
if (clauseNode.getCatchCondition() != null) {
errorReporter.error(
"Catch clauses are not supported",
sourceName,
clauseNode.getCatchCondition().getLineno(), "", 0);
}
node.addChildToBack(transformBlock(clauseNode.getBody()));
return node;
}
@Override
Node processConditionalExpression(ConditionalExpression exprNode) {
return newNode(
Token.HOOK,
transform(exprNode.getTestExpression()),
transform(exprNode.getTrueExpression()),
transform(exprNode.getFalseExpression()));
}
@Override
Node processContinueStatement(ContinueStatement statementNode) {
Node node = newNode(Token.CONTINUE);
if (statementNode.getLabel() != null) {
Node labelName = transform(statementNode.getLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processDoLoop(DoLoop loopNode) {
return newNode(
Token.DO,
transformBlock(loopNode.getBody()),
transform(loopNode.getCondition()));
}
@Override
Node processElementGet(ElementGet getNode) {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
return newNode(
Token.GETELEM,
transform(getNode.getTarget()),
transform(getNode.getElement()));
}
@Override
Node processEmptyExpression(EmptyExpression exprNode) {
Node node = newNode(Token.EMPTY);
return node;
}
@Override
Node processExpressionStatement(ExpressionStatement statementNode) {
Node node = newNode(transformTokenType(statementNode.getType()));
node.addChildToBack(transform(statementNode.getExpression()));
return node;
}
@Override
Node processForInLoop(ForInLoop loopNode) {
return newNode(
Token.FOR,
transform(loopNode.getIterator()),
transform(loopNode.getIteratedObject()),
transformBlock(loopNode.getBody()));
}
@Override
Node processForLoop(ForLoop loopNode) {
Node node = newNode(
Token.FOR,
transform(loopNode.getInitializer()),
transform(loopNode.getCondition()),
transform(loopNode.getIncrement()));
node.addChildToBack(transformBlock(loopNode.getBody()));
return node;
}
@Override
Node processFunctionCall(FunctionCall callNode) {
Node node = newNode(transformTokenType(callNode.getType()),
transform(callNode.getTarget()));
for (AstNode child : callNode.getArguments()) {
node.addChildToBack(transform(child));
}
int leftParamPos = callNode.getAbsolutePosition() + callNode.getLp();
node.setLineno(callNode.getLineno());
node.setCharno(position2charno(leftParamPos));
return node;
}
@Override
Node processFunctionNode(FunctionNode functionNode) {
Name name = functionNode.getFunctionName();
Boolean isUnnamedFunction = false;
if (name == null) {
int functionType = functionNode.getFunctionType();
if (functionType != FunctionNode.FUNCTION_EXPRESSION) {
errorReporter.error(
"unnamed function statement",
sourceName,
functionNode.getLineno(), "", 0);
}
name = new Name();
name.setIdentifier("");
isUnnamedFunction = true;
}
Node node = newNode(Token.FUNCTION);
Node newName = transform(name);
if (isUnnamedFunction) {
// Old Rhino tagged the empty name node with the line number of the
// declaration.
newName.setLineno(functionNode.getLineno());
// TODO(bowdidge) Mark line number of paren correctly.
// Same problem as below - the left paren might not be on the
// same line as the function keyword.
int lpColumn = functionNode.getAbsolutePosition() +
functionNode.getLp();
newName.setCharno(position2charno(lpColumn));
}
node.addChildToBack(newName);
Node lp = newNode(Token.LP);
// The left paren's complicated because it's not represented by an
// AstNode, so there's nothing that has the actual line number that it
// appeared on. We know the paren has to appear on the same line as the
// function name (or else a semicolon will be inserted.) If there's no
// function
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> name, assume the paren was on the same line as the function.
// TODO(bowdidge): Mark line number of paren correctly.
Name fnName = functionNode.getFunctionName();
if (fnName != null) {
lp.setLineno(fnName.getLineno());
} else {
lp.setLineno(functionNode.getLineno());
}
int lparenCharno = functionNode.getLp() +
functionNode.getAbsolutePosition();
lp.setCharno(position2charno(lparenCharno));
for (AstNode param : functionNode.getParams()) {
lp.addChildToBack(transform(param));
}
node.addChildToBack(lp);
Node bodyNode = transform(functionNode.getBody());
parseDirectives(bodyNode);
node.addChildToBack(bodyNode);
return node;
}
@Override
Node processIfStatement(IfStatement statementNode) {
Node node = newNode(Token.IF);
node.addChildToBack(transform(statementNode.getCondition()));
node.addChildToBack(transformBlock(statementNode.getThenPart()));
if (statementNode.getElsePart() != null) {
node.addChildToBack(transformBlock(statementNode.getElsePart()));
}
return node;
}
@Override
Node processInfixExpression(InfixExpression exprNode) {
Node n = newNode(
transformTokenType(exprNode.getType()),
transform(exprNode.getLeft()),
transform(exprNode.getRight()));
// Set the line number here so we can fine-tune it in ways transform
// doesn't do.
n.setLineno(exprNode.getLineno());
// Position in new ASTNode is to start of expression, but old-fashioned
// line numbers from Node reference the operator token. Add the offset
// to the operator to get the correct character number.
n.setCharno(position2charno(exprNode.getAbsolutePosition() +
exprNode.getOperatorPosition()));
return n;
}
@Override
Node processKeywordLiteral(KeywordLiteral literalNode) {
return newNode(transformTokenType(literalNode.getType()));
}
@Override
Node processLabel(Label labelNode) {
return newStringNode(Token.LABEL_NAME, labelNode.getName());
}
@Override
Node processLabeledStatement(LabeledStatement statementNode) {
Node node = newNode(Token.LABEL);
Node prev = null;
Node cur = node;
for (Label label : statementNode.getLabels()) {
if (prev != null) {
prev.addChildToBack(cur);
}
cur.addChildToBack(transform(label));
cur.setLineno(label.getLineno());
int clauseAbsolutePosition =
position2charno(label.getAbsolutePosition());
cur.setCharno(clauseAbsolutePosition);
prev = cur;
cur = newNode(Token.LABEL);
}
prev.addChildToBack(transform(statementNode.getStatement()));
return node;
}
@Override
Node processName(Name nameNode) {
return processName(nameNode, false);
}
Node processName
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>(Name nameNode, boolean asString) {
if (asString) {
return newStringNode(Token.STRING, nameNode.getIdentifier());
} else {
if (isReservedKeyword(nameNode.getIdentifier())) {
errorReporter.error(
"identifier is a reserved word",
sourceName,
nameNode.getLineno(), "", 0);
}
return newStringNode(Token.NAME, nameNode.getIdentifier());
}
}
/**
* @return Whether the
*/
private boolean isReservedKeyword(String identifier) {
return reservedKeywords != null && reservedKeywords.contains(identifier);
}
@Override
Node processNewExpression(NewExpression exprNode) {
return processFunctionCall(exprNode);
}
@Override
Node processNumberLiteral(NumberLiteral literalNode) {
return newNumberNode(literalNode.getNumber());
}
@Override
Node processObjectLiteral(ObjectLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.OBJECTLIT);
for (ObjectProperty el : literalNode.getElements()) {
if (config.languageMode == LanguageMode.ECMASCRIPT3) {
if (el.isGetter()) {
reportGetter(el);
continue;
} else if (el.isSetter()) {
reportSetter(el);
continue;
}
}
Node key = transformAsString(el.getLeft());
Node value = transform(el.getRight());
if (el.isGetter()) {
key.setType(Token.GET);
Preconditions.checkState(value.getType() == Token.FUNCTION);
if (getFnParamNode(value).hasChildren()) {
reportGetterParam(el.getLeft());
}
} else if (el.isSetter()) {
key.setType(Token.SET);
Preconditions.checkState(value.getType() == Token.FUNCTION);
if (!getFnParamNode(value).hasOneChild()) {
reportSetterParam(el.getLeft());
}
}
key.addChildToFront(value);
node.addChildToBack(key);
}
return node;
}
/**
* @param fnNode The function.
* @return The Node containing the Function parameters.
*/
Node getFnParamNode(Node fnNode) {
// Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.getType() == Token.FUNCTION);
return fnNode.getFirstChild().getNext();
}
@Override
Node processObjectProperty(ObjectProperty propertyNode) {
return processInfixExpression(propertyNode);
}
@Override
Node processParenthesizedExpression(ParenthesizedExpression exprNode) {
Node node = transform(exprNode.getExpression());
node.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE);
return node;
}
@Override
Node processPropertyGet(PropertyGet getNode) {
return newNode(
Token.GETPROP,
transform(getNode.getTarget()),
transformAsString(getNode.getProperty()));
}
@Override
Node processRegExpLiteral(RegExpLiteral literalNode) {
Node literalStringNode
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> = newStringNode(literalNode.getValue());
// assume it's on the same line.
literalStringNode.setLineno(literalNode.getLineno());
Node node = newNode(Token.REGEXP, literalStringNode);
String flags = literalNode.getFlags();
if (flags != null && !flags.isEmpty()) {
Node flagsNode = newStringNode(flags);
// Assume the flags are on the same line as the literal node.
flagsNode.setLineno(literalNode.getLineno());
node.addChildToBack(flagsNode);
}
return node;
}
@Override
Node processReturnStatement(ReturnStatement statementNode) {
Node node = newNode(Token.RETURN);
if (statementNode.getReturnValue() != null) {
node.addChildToBack(transform(statementNode.getReturnValue()));
}
return node;
}
@Override
Node processScope(Scope scopeNode) {
return processGeneric(scopeNode);
}
@Override
Node processStringLiteral(StringLiteral literalNode) {
Node n = newStringNode(literalNode.getValue());
return n;
}
@Override
Node processSwitchCase(SwitchCase caseNode) {
Node node;
if (caseNode.isDefault()) {
node = newNode(Token.DEFAULT);
} else {
AstNode expr = caseNode.getExpression();
node = newNode(Token.CASE, transform(expr));
}
Node block = newNode(Token.BLOCK);
block.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true);
block.setLineno(caseNode.getLineno());
block.setCharno(position2charno(caseNode.getAbsolutePosition()));
if (caseNode.getStatements() != null) {
for (AstNode child : caseNode.getStatements()) {
block.addChildToBack(transform(child));
}
}
node.addChildToBack(block);
return node;
}
@Override
Node processSwitchStatement(SwitchStatement statementNode) {
Node node = newNode(Token.SWITCH,
transform(statementNode.getExpression()));
for (AstNode child : statementNode.getCases()) {
node.addChildToBack(transform(child));
}
return node;
}
@Override
Node processThrowStatement(ThrowStatement statementNode) {
return newNode(Token.THROW,
transform(statementNode.getExpression()));
}
@Override
Node processTryStatement(TryStatement statementNode) {
Node node = newNode(Token.TRY,
transformBlock(statementNode.getTryBlock()));
Node block = newNode(Token.BLOCK);
boolean lineSet = false;
for (CatchClause cc : statementNode.getCatchClauses()) {
// Mark the enclosing block at the same line as the first catch
// clause.
if (lineSet == false) {
block.setLineno(cc.getLineno());
lineSet = true;
}
block.addChildToBack(transform(cc));
}
node.addChildToBack(block);
AstNode finallyBlock = statementNode.getFinallyBlock();
if (finallyBlock != null) {
node.addChildToBack(transform
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>Block(finallyBlock));
}
// If we didn't set the line on the catch clause, then
// we've got an empty catch clause. Set its line to be the same
// as the finally block (to match Old Rhino's behavior.)
if ((lineSet == false) && (finallyBlock != null)) {
block.setLineno(finallyBlock.getLineno());
}
return node;
}
@Override
Node processUnaryExpression(UnaryExpression exprNode) {
int type = transformTokenType(exprNode.getType());
Node operand = transform(exprNode.getOperand());
if (type == Token.NEG && operand.getType() == Token.NUMBER) {
operand.setDouble(-operand.getDouble());
return operand;
} else {
if (type == Token.INC || type == Token.DEC) {
if (!validAssignmentTarget(operand)) {
String msg = (type == Token.INC)
? "invalid increment target"
: "invalid decrement target";
errorReporter.error(
msg,
sourceName,
operand.getLineno(), "", 0);
}
}
Node node = newNode(type, operand);
if (exprNode.isPostfix()) {
node.putBooleanProp(Node.INCRDECR_PROP, true);
}
return node;
}
}
private boolean validAssignmentTarget(Node target) {
switch (target.getType()) {
case Token.NAME:
case Token.GETPROP:
case Token.GETELEM:
return true;
}
return false;
}
@Override
Node processVariableDeclaration(VariableDeclaration declarationNode) {
if (!config.acceptConstKeyword && declarationNode.getType() ==
com.google.javascript.jscomp.mozilla.rhino.Token.CONST) {
processIllegalToken(declarationNode);
}
Node node = newNode(Token.VAR);
for (VariableInitializer child : declarationNode.getVariables()) {
node.addChildToBack(transform(child));
}
return node;
}
@Override
Node processVariableInitializer(VariableInitializer initializerNode) {
Node node = transform(initializerNode.getTarget());
if (initializerNode.getInitializer() != null) {
node.addChildToBack(transform(initializerNode.getInitializer()));
node.setLineno(node.getLineno());
}
return node;
}
@Override
Node processWhileLoop(WhileLoop loopNode) {
return newNode(
Token.WHILE,
transform(loopNode.getCondition()),
transformBlock(loopNode.getBody()));
}
@Override
Node processWithStatement(WithStatement statementNode) {
return newNode(
Token.WITH,
transform(statementNode.getExpression()),
transformBlock(statementNode.getStatement()));
}
@Override
Node processIllegalToken(AstNode node) {
errorReporter.error(
"Unsupported syntax: " +
com.google.javascript.jscomp.mozilla.rhino.Token.typeToName(
node.getType()),
sourceName,
node.getLineno(), "", 0);
return newNode(Token.EMPTY);
}
void reportDestructuringAssign(AstNode node) {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> errorReporter.error(
"destructuring assignment forbidden",
sourceName,
node.getLineno(), "", 0);
}
void reportGetter(AstNode node) {
errorReporter.error(
"getters are not supported in Internet Explorer",
sourceName,
node.getLineno(), "", 0);
}
void reportSetter(AstNode node) {
errorReporter.error(
"setters are not supported in Internet Explorer",
sourceName,
node.getLineno(), "", 0);
}
void reportGetterParam(AstNode node) {
errorReporter.error(
"getters may not have parameters",
sourceName,
node.getLineno(), "", 0);
}
void reportSetterParam(AstNode node) {
errorReporter.error(
"setters must have exactly one parameter",
sourceName,
node.getLineno(), "", 0);
}
}
private static int transformTokenType(int token) {
switch (token) {
case com.google.javascript.jscomp.mozilla.rhino.Token.RETURN:
return Token.RETURN;
case com.google.javascript.jscomp.mozilla.rhino.Token.BITOR:
return Token.BITOR;
case com.google.javascript.jscomp.mozilla.rhino.Token.BITXOR:
return Token.BITXOR;
case com.google.javascript.jscomp.mozilla.rhino.Token.BITAND:
return Token.BITAND;
case com.google.javascript.jscomp.mozilla.rhino.Token.EQ:
return Token.EQ;
case com.google.javascript.jscomp.mozilla.rhino.Token.NE:
return Token.NE;
case com.google.javascript.jscomp.mozilla.rhino.Token.LT:
return Token.LT;
case com.google.javascript.jscomp.mozilla.rhino.Token.LE:
return Token.LE;
case com.google.javascript.jscomp.mozilla.rhino.Token.GT:
return Token.GT;
case com.google.javascript.jscomp.mozilla.rhino.Token.GE:
return Token.GE;
case com.google.javascript.jscomp.mozilla.rhino.Token.LSH:
return Token.LSH;
case com.google.javascript.jscomp.mozilla.rhino.Token.RSH:
return Token.RSH;
case com.google.javascript.jscomp.mozilla.rhino.Token.URSH:
return Token.URSH;
case com.google.javascript.jscomp.mozilla.rhino.Token.ADD:
return Token.ADD;
case com.google.javascript.jscomp.mozilla.rhino.Token.SUB:
return Token.SUB;
case com.google.javascript.jscomp.mozilla.rhino.Token.MUL:
return Token.MUL;
case com.google.javascript.jscomp.mozilla.rhino.Token.DIV:
return Token.DIV;
case com.google.javascript.jscomp.mozilla.rhino.Token.MOD:
return Token.MOD;
case com.google
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>.javascript.jscomp.mozilla.rhino.Token.NOT:
return Token.NOT;
case com.google.javascript.jscomp.mozilla.rhino.Token.BITNOT:
return Token.BITNOT;
case com.google.javascript.jscomp.mozilla.rhino.Token.POS:
return Token.POS;
case com.google.javascript.jscomp.mozilla.rhino.Token.NEG:
return Token.NEG;
case com.google.javascript.jscomp.mozilla.rhino.Token.NEW:
return Token.NEW;
case com.google.javascript.jscomp.mozilla.rhino.Token.DELPROP:
return Token.DELPROP;
case com.google.javascript.jscomp.mozilla.rhino.Token.TYPEOF:
return Token.TYPEOF;
case com.google.javascript.jscomp.mozilla.rhino.Token.GETPROP:
return Token.GETPROP;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETPROP:
return Token.SETPROP;
case com.google.javascript.jscomp.mozilla.rhino.Token.GETELEM:
return Token.GETELEM;
case com.google.javascript.jscomp.mozilla.rhino.Token.SETELEM:
return Token.SETELEM;
case com.google.javascript.jscomp.mozilla.rhino.Token.CALL:
return Token.CALL;
case com.google.javascript.jscomp.mozilla.rhino.Token.NAME:
return Token.NAME;
case com.google.javascript.jscomp.mozilla.rhino.Token.NUMBER:
return Token.NUMBER;
case com.google.javascript.jscomp.mozilla.rhino.Token.STRING:
return Token.STRING;
case com.google.javascript.jscomp.mozilla.rhino.Token.NULL:
return Token.NULL;
case com.google.javascript.jscomp.mozilla.rhino.Token.THIS:
return Token.THIS;
case com.google.javascript.jscomp.mozilla.rhino.Token.FALSE:
return Token.FALSE;
case com.google.javascript.jscomp.mozilla.rhino.Token.TRUE:
return Token.TRUE;
case com.google.javascript.jscomp.mozilla.rhino.Token.SHEQ:
return Token.SHEQ;
case com.google.javascript.jscomp.mozilla.rhino.Token.SHNE:
return Token.SHNE;
case com.google.javascript.jscomp.mozilla.rhino.Token.REGEXP:
return Token.REGEXP;
case com.google.javascript.jscomp.mozilla.rhino.Token.THROW:
return Token.THROW;
case com.google.javascript.jscomp.mozilla.rhino.Token.IN:
return Token.IN;
case com.google.javascript.jscomp.mozilla.rhino.Token.INSTANCEOF:
return Token.INSTANCEOF;
case com.google.javascript.jscomp.mozilla.rhino.Token.ARRAYLIT:
return Token.ARRAYLIT;
case com.google.javascript.jscomp.
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>mozilla.rhino.Token.OBJECTLIT:
return Token.OBJECTLIT;
case com.google.javascript.jscomp.mozilla.rhino.Token.TRY:
return Token.TRY;
// The LP represents a parameter list
case com.google.javascript.jscomp.mozilla.rhino.Token.LP:
return Token.LP;
case com.google.javascript.jscomp.mozilla.rhino.Token.COMMA:
return Token.COMMA;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN:
return Token.ASSIGN;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITOR:
return Token.ASSIGN_BITOR;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITXOR:
return Token.ASSIGN_BITXOR;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITAND:
return Token.ASSIGN_BITAND;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_LSH:
return Token.ASSIGN_LSH;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_RSH:
return Token.ASSIGN_RSH;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_URSH:
return Token.ASSIGN_URSH;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_ADD:
return Token.ASSIGN_ADD;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_SUB:
return Token.ASSIGN_SUB;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MUL:
return Token.ASSIGN_MUL;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_DIV:
return Token.ASSIGN_DIV;
case com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MOD:
return Token.ASSIGN_MOD;
case com.google.javascript.jscomp.mozilla.rhino.Token.HOOK:
return Token.HOOK;
case com.google.javascript.jscomp.mozilla.rhino.Token.COLON:
return Token.COLON;
case com.google.javascript.jscomp.mozilla.rhino.Token.OR:
return Token.OR;
case com.google.javascript.jscomp.mozilla.rhino.Token.AND:
return Token.AND;
case com.google.javascript.jscomp.mozilla.rhino.Token.INC:
return Token.INC;
case com.google.javascript.jscomp.mozilla.rhino.Token.DEC:
return Token.DEC;
case com.google.javascript.jscomp.mozilla.rhino.Token.FUNCTION:
return Token.FUNCTION;
case com.google.javascript.jscomp.mozilla.rhino.Token.IF:
return Token.IF;
case com.google.javascript.jscomp.mozilla.rhino.Token.ELSE:
return Token.ELSE;
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> case com.google.javascript.jscomp.mozilla.rhino.Token.SWITCH:
return Token.SWITCH;
case com.google.javascript.jscomp.mozilla.rhino.Token.CASE:
return Token.CASE;
case com.google.javascript.jscomp.mozilla.rhino.Token.DEFAULT:
return Token.DEFAULT;
case com.google.javascript.jscomp.mozilla.rhino.Token.WHILE:
return Token.WHILE;
case com.google.javascript.jscomp.mozilla.rhino.Token.DO:
return Token.DO;
case com.google.javascript.jscomp.mozilla.rhino.Token.FOR:
return Token.FOR;
case com.google.javascript.jscomp.mozilla.rhino.Token.BREAK:
return Token.BREAK;
case com.google.javascript.jscomp.mozilla.rhino.Token.CONTINUE:
return Token.CONTINUE;
case com.google.javascript.jscomp.mozilla.rhino.Token.VAR:
return Token.VAR;
case com.google.javascript.jscomp.mozilla.rhino.Token.WITH:
return Token.WITH;
case com.google.javascript.jscomp.mozilla.rhino.Token.CATCH:
return Token.CATCH;
case com.google.javascript.jscomp.mozilla.rhino.Token.FINALLY:
return Token.FINALLY;
case com.google.javascript.jscomp.mozilla.rhino.Token.VOID:
return Token.VOID;
case com.google.javascript.jscomp.mozilla.rhino.Token.EMPTY:
return Token.EMPTY;
case com.google.javascript.jscomp.mozilla.rhino.Token.BLOCK:
return Token.BLOCK;
case com.google.javascript.jscomp.mozilla.rhino.Token.LABEL:
return Token.LABEL;
case com.google.javascript.jscomp.mozilla.rhino.Token.EXPR_VOID:
case com.google.javascript.jscomp.mozilla.rhino.Token.EXPR_RESULT:
return Token.EXPR_RESULT;
case com.google.javascript.jscomp.mozilla.rhino.Token.SCRIPT:
return Token.SCRIPT;
case com.google.javascript.jscomp.mozilla.rhino.Token.GET:
return Token.GET;
case com.google.javascript.jscomp.mozilla.rhino.Token.SET:
return Token.SET;
case com.google.javascript.jscomp.mozilla.rhino.Token.CONST:
return Token.CONST;
case com.google.javascript.jscomp.mozilla.rhino.Token.DEBUGGER:
return Token.DEBUGGER;
}
// Token without name
throw new IllegalStateException(String.valueOf(token));
}
// Simple helper to create nodes and set the initial node properties.
private Node newNode(int type) {
return new Node(type).clonePropsFrom(templateNode);
}
private Node newNode(int type, Node child1) {
return new Node(type, child1).clonePropsFrom(templateNode);
}
private Node newNode(int type, Node child1, Node child
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> = reference.getGrandparent();
if (grandparent.getType() == Token.NAME
&& grandparent.getString() == v.name) {
continue;
}
// Only generate warnings if the scopes do not match in order
// to deal with possible forward declarations and recursion
if (reference.getScope() == v.scope) {
compiler.report(
JSError.make(reference.getSourceName(),
reference.getNameNode(),
checkLevel,
UNDECLARED_REFERENCE, v.name));
}
}
if (isDeclaration) {
blocksWithDeclarations.add(basicBlock);
isDeclaredInScope = true;
}
}
}
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>();
}
if (jsRoot != null) {
jsRoot.detachChildren();
}
// Parse main js sources.
jsRoot = new Node(Token.BLOCK);
jsRoot.setIsSyntheticBlock(true);
externsRoot = new Node(Token.BLOCK);
externsRoot.setIsSyntheticBlock(true);
externAndJsRoot = new Node(Token.BLOCK, externsRoot, jsRoot);
externAndJsRoot.setIsSyntheticBlock(true);
if (options.tracer.isOn()) {
tracker = new PerformanceTracker(jsRoot,
options.tracer == TracerMode.ALL);
addChangeHandler(tracker.getCodeChangeHandler());
}
Tracer tracer = newTracer("parseInputs");
try {
// Parse externs sources.
for (CompilerInput input : externs) {
Node n = input.getAstRoot(this);
if (hasErrors()) {
return null;
}
externsRoot.addChildToBack(n);
}
// Check if the sources need to be re-ordered.
if (options.manageClosureDependencies) {
for (CompilerInput input : inputs) {
input.setCompiler(this);
// Forward-declare all the provided types, so that they
// are not flagged even if they are dropped from the process.
for (String provide : input.getProvides()) {
getTypeRegistry().forwardDeclareType(provide);
}
}
try {
inputs =
(moduleGraph == null ? new JSModuleGraph(modules) : moduleGraph)
.manageDependencies(
options.manageClosureDependenciesEntryPoints, inputs);
} catch (CircularDependencyException e) {
report(JSError.make(
JSModule.CIRCULAR_DEPENDENCY_ERROR, e.getMessage()));
return null;
} catch (MissingProvideException e) {
report(JSError.make(
MISSING_ENTRY_ERROR, e.getMessage()));
return null;
}
}
// Check if inputs need to be rebuilt from modules.
boolean staleInputs = false;
for (CompilerInput input : inputs) {
Node n = input.getAstRoot(this);
// Inputs can have a null AST during initial parse.
if (n == null) {
continue;
}
if (n.getJSDocInfo() != null) {
JSDocInfo info = n.getJSDocInfo();
if (info.isExterns()) {
// If the input file is explicitly marked as an externs file, then
// assume the programmer made a mistake and throw it into
// the externs pile anyways.
externsRoot.addChildToBack(n);
input.setIsExtern(true);
input.getModule().remove(input);
externs.add(input);
staleInputs = true;
} else if (info.isNoCompile()) {
input.getModule().remove(input);
staleInputs = true;
}
}
}
if (staleInputs) {
fillEmptyModules(modules);
rebuildInputsFromModules();
}
// Build the AST.
for (CompilerInput input : inputs) {
Node n = input.getAstRoot(this);
if
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> // Make sure that the label starts on a new line
}
Preconditions.checkState(root.getType() == Token.SCRIPT);
String delimiter = options.inputDelimiter;
String sourceName = (String)root.getProp(Node.SOURCENAME_PROP);
Preconditions.checkState(sourceName != null);
Preconditions.checkState(!sourceName.isEmpty());
delimiter = delimiter.replaceAll("%name%", sourceName)
.replaceAll("%num%", String.valueOf(inputSeqNum));
cb.append(delimiter)
.append("\n");
}
if (root.getJSDocInfo() != null &&
root.getJSDocInfo().getLicense() != null) {
cb.append("/*\n")
.append(root.getJSDocInfo().getLicense())
.append("*/\n");
}
// If there is a valid source map, then indicate to it that the current
// root node's mappings are offset by the given string builder buffer.
if (options.sourceMapOutputPath != null) {
sourceMap.setStartingPosition(
cb.getLineIndex(), cb.getColumnIndex());
}
String code = toSource(root, sourceMap);
if (!code.isEmpty()) {
cb.append(code);
// In order to avoid parse ambiguity when files are concatenated
// together, all files should end in a semi-colon. Do a quick
// heuristic check if there's an obvious semi-colon already there.
int length = code.length();
char lastChar = code.charAt(length - 1);
char secondLastChar = length >= 2 ?
code.charAt(length - 2) : '\0';
boolean hasSemiColon = lastChar == ';' ||
(lastChar == '\n' && secondLastChar == ';');
if (!hasSemiColon) {
cb.append(";");
}
}
return null;
}
});
}
/**
* Generates JavaScript source code for an AST, doesn't generate source
* map info.
*/
@Override
String toSource(Node n) {
initCompilerOptionsIfTesting();
return toSource(n, null);
}
/**
* Generates JavaScript source code for an AST.
*/
private String toSource(Node n, SourceMap sourceMap) {
CodePrinter.Builder builder = new CodePrinter.Builder(n);
builder.setPrettyPrint(options.prettyPrint);
builder.setLineBreak(options.lineBreak);
builder.setSourceMap(sourceMap);
builder.setSourceMapDetailLevel(options.sourceMapDetailLevel);
builder.setTagAsStrict(
options.getLanguageOut() == LanguageMode.ECMASCRIPT5_STRICT);
builder.setLineLengthThreshold(options.lineLengthThreshold);
Charset charset = options.outputCharset != null ?
Charset.forName(options.outputCharset) : null;
builder.setOutputCharset(charset);
return builder.build();
}
/**
* Stores a buffer of text to which more can be appended. This is just like a
* StringBuilder except that we also track the number of lines.
*/
public static class CodeBuilder {
private final StringBuilder sb = new StringBuilder
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>> modules;
private PassConfig.State passConfigState;
private JSTypeRegistry typeRegistry;
private AbstractCompiler.LifeCycleStage lifeCycleStage;
private IntermediateState() {}
}
/**
* Returns the current internal state, excluding the input files and modules.
*/
public IntermediateState getState() {
IntermediateState state = new IntermediateState();
state.externsRoot = externsRoot;
state.jsRoot = jsRoot;
state.externs = externs;
state.inputs = inputs;
state.modules = modules;
state.passConfigState = getPassConfig().getIntermediateState();
state.typeRegistry = typeRegistry;
state.lifeCycleStage = getLifeCycleStage();
return state;
}
/**
* Sets the internal state to the capture given. Note that this assumes that
* the input files are already set up.
*/
public void setState(IntermediateState state) {
externsRoot = state.externsRoot;
jsRoot = state.jsRoot;
externs = state.externs;
inputs = state.inputs;
modules = state.modules;
passes = createPassConfigInternal();
getPassConfig().setIntermediateState(state.passConfigState);
typeRegistry = state.typeRegistry;
setLifeCycleStage(state.lifeCycleStage);
}
@VisibleForTesting
List<CompilerInput> getInputsForTesting() {
return inputs;
}
@VisibleForTesting
List<CompilerInput> getExternsForTesting() {
return externs;
}
@Override
boolean hasRegExpGlobalReferences() {
return hasRegExpGlobalReferences;
}
@Override
void setHasRegExpGlobalReferences(boolean references) {
hasRegExpGlobalReferences = references;
}
@Override
void updateGlobalVarReferences(Map<Var, ReferenceCollection> refMapPatch,
Node collectionRoot) {
Preconditions.checkState(collectionRoot.getType() == Token.SCRIPT
|| collectionRoot.getType() == Token.BLOCK);
if (globalRefMap == null) {
globalRefMap = new GlobalVarReferenceMap(getInputsInOrder());
}
globalRefMap.updateGlobalVarReferences(refMapPatch, collectionRoot);
}
@Override
ReferenceMap getGlobalVarReferences() {
return globalRefMap;
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.parsing.ParserRunner;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.io.IOException;
import java.util.logging.Logger;
/**
* Generates an AST for a JavaScript source file.
*
*/
public class JsAst implements SourceAst {
private static final Logger logger_ = Logger.getLogger(JsAst.class.getName());
private static final long serialVersionUID = 1L;
private transient SourceFile sourceFile;
private String fileName;
private Node root;
public JsAst(SourceFile sourceFile) {
this.sourceFile = sourceFile;
this.fileName = sourceFile.getName();
}
@Override
public Node getAstRoot(AbstractCompiler compiler) {
if (root == null) {
createAst(compiler);
}
return root;
}
@Override
public void clearAst() {
root = null;
// While we're at it, clear out any saved text in the source file on
// the assumption that if we're dumping the parse tree, then we probably
// assume regenerating everything else is a smart idea also.
sourceFile.clearCachedSource();
}
@Override
public SourceFile getSourceFile() {
return sourceFile;
}
@Override
public void setSourceFile(SourceFile file) {
Preconditions.checkState(fileName.equals(file.getName()));
sourceFile = file;
}
private void createAst(AbstractCompiler compiler) {
try {
parse(compiler, sourceFile.getName(), sourceFile.getCode());
} catch (IOException e) {
compiler.report(
JSError.make(AbstractCompiler.READ_ERROR, sourceFile.getName()));
}
}
private void parse(AbstractCompiler compiler, String sourceName,
String sourceStr) {
try {
logger_.fine("Parsing: " + sourceName);
root = ParserRunner.parse(sourceName, sourceStr,
compiler.getParserConfig(),
compiler.getDefaultErrorReporter(),
logger_);
} catch (IOException e) {
compiler.report(JSError.make(AbstractCompiler.READ_ERROR, sourceName));
}
if (root == null || compiler.hasHaltingErrors()) {
// There was a parse error or IOException, so use a dummy block.
root = new Node(Token.BLOCK);
} else {
compiler.prepareAst
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> a type
* in this group.
*/
public boolean matches(JSError error) {
return matches(error.getType());
}
/**
* Returns whether the given type matches a type in this group.
*/
public boolean matches(DiagnosticType type) {
return types.contains(type);
}
/**
* Returns whether all of the types in the given group are in this group.
*/
boolean isSubGroup(DiagnosticGroup group) {
for (DiagnosticType type : group.types) {
if (!matches(type)) {
return false;
}
}
return true;
}
/**
* Returns an iterator over all the types in this group.
*/
Collection<DiagnosticType> getTypes() {
return types;
}
public String toString() {
return name == null ? super.toString() : "DiagnosticGroup<" + name + ">";
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> @Override
public boolean isNominalType() {
return true;
}
/**
* Two named types are equivalent if they are the same {@code
* ObjectType} object. This is complicated by the fact that isEquivalent
* is sometimes called before we have a chance to resolve the type
* names.
*
* @return {@code true} iff {@code that} == {@code this} or {@code that}
* is a {@link NamedType} whose reference is the same as ours,
* or {@code that} is the type we reference.
*/
@Override
public boolean isEquivalentTo(JSType that) {
if (this == that) {
return true;
}
ObjectType objType = ObjectType.cast(that);
if (objType != null) {
return objType.isNominalType() &&
reference.equals(objType.getReferenceName());
}
return false;
}
@Override
public int hashCode() {
return reference.hashCode();
}
/**
* Resolve the referenced type within the enclosing scope.
*/
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> enclosing) {
// TODO(user): Investigate whether it is really necessary to keep two
// different mechanisms for resolving named types, and if so, which order
// makes more sense. Now, resolution via registry is first in order to
// avoid triggering the warnings built into the resolution via properties.
boolean resolved = resolveViaRegistry(t, enclosing);
if (detectImplicitPrototypeCycle()) {
handleTypeCycle(t);
}
if (resolved) {
super.resolveInternal(t, enclosing);
finishPropertyContinuations();
return registry.isLastGeneration() ?
getReferencedType() : this;
}
resolveViaProperties(t, enclosing);
if (detectImplicitPrototypeCycle()) {
handleTypeCycle(t);
}
super.resolveInternal(t, enclosing);
if (isResolved()) {
finishPropertyContinuations();
}
return registry.isLastGeneration() ?
getReferencedType() : this;
}
/**
* Resolves a named type by looking it up in the registry.
* @return True if we resolved successfully.
*/
private boolean resolveViaRegistry(
ErrorReporter t, StaticScope<JSType> enclosing) {
JSType type = registry.getType(reference);
if (type != null) {
setReferencedAndResolvedType(type, t, enclosing);
return true;
}
return false;
}
/**
* Resolves a named type by looking up its first component in the scope, and
* subsequent components as properties. The scope must have been fully
* parsed and a symbol table constructed.
*/
private void resolveViaProperties(ErrorReporter t,
StaticScope<JSType> enclosing) {
JSType value = lookupViaProperties(t, enclosing);
// last component of the chain
if ((value instanceof FunctionType) &&
(value.isConstructor() || value.isInterface())) {
FunctionType functionType = (FunctionType) value;
setReferencedAndResolvedType(
functionType.getInstanceType(), t, enclosing);
} else if (value
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> instanceof EnumType) {
setReferencedAndResolvedType(
((EnumType) value).getElementsType(), t, enclosing);
} else {
// We've been running into issues where people forward-declare
// non-named types. (This is legitimate...our dependency management
// code doubles as our forward-declaration code.)
//
// So if the type does resolve to an actual value, but it's not named,
// then don't respect the forward declaration.
handleUnresolvedType(t, value == null || value.isUnknownType());
}
}
/**
* Resolves a type by looking up its first component in the scope, and
* subsequent components as properties. The scope must have been fully
* parsed and a symbol table constructed.
* @return The type of the symbol, or null if the type could not be found.
*/
private JSType lookupViaProperties( ErrorReporter t,
StaticScope<JSType> enclosing) {
String[] componentNames = reference.split("\\.", -1);
if (componentNames[0].length() == 0) {
return null;
}
StaticSlot<JSType> slot = enclosing.getSlot(componentNames[0]);
if (slot == null) {
return null;
}
// If the first component has a type of 'Unknown', then any type
// names using it should be regarded as silently 'Unknown' rather than be
// noisy about it.
JSType slotType = slot.getType();
if (slotType == null || slotType.isAllType() || slotType.isNoType()) {
return null;
}
JSType value = getTypedefType(t, slot, componentNames[0]);
if (value == null) {
return null;
}
// resolving component by component
for (int i = 1; i < componentNames.length; i++) {
ObjectType parentClass = ObjectType.cast(value);
if (parentClass == null) {
return null;
}
if (componentNames[i].length() == 0) {
return null;
}
value = parentClass.getPropertyType(componentNames[i]);
}
return value;
}
private void setReferencedAndResolvedType(JSType type, ErrorReporter t,
StaticScope<JSType> enclosing) {
if (validator != null) {
validator.apply(type);
}
setReferencedType(type);
checkEnumElementCycle(t);
setResolvedTypeInternal(getReferencedType());
}
private void handleTypeCycle(ErrorReporter t) {
setReferencedType(
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE));
t.warning("Cycle detected in inheritance chain of type " + reference,
sourceName, lineno, null, charno);
setResolvedTypeInternal(getReferencedType());
}
private void checkEnumElementCycle(ErrorReporter t) {
JSType referencedType = getReferencedType();
if (referencedType instanceof EnumElementType &&
((EnumElementType) referencedType).getPrimitiveType() == this) {
handleTypeCycle(t);
}
}
// Warns about this type being unresolved iff it's not a forward-declared
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> // type name.
private void handleUnresolvedType(
ErrorReporter t, boolean ignoreForwardReferencedTypes) {
if (registry.isLastGeneration()) {
boolean isForwardDeclared =
ignoreForwardReferencedTypes &&
registry.isForwardDeclaredType(reference);
if (!isForwardDeclared && registry.isLastGeneration()) {
t.warning("Bad type annotation. Unknown type " + reference,
sourceName, lineno, null, charno);
} else {
setReferencedType(
registry.getNativeObjectType(
JSTypeNative.NO_RESOLVED_TYPE));
if (registry.isLastGeneration() && validator != null) {
validator.apply(getReferencedType());
}
}
setResolvedTypeInternal(getReferencedType());
} else {
setResolvedTypeInternal(this);
}
}
JSType getTypedefType(ErrorReporter t, StaticSlot<JSType> slot, String name) {
JSType type = slot.getType();
if (type != null) {
return type;
}
handleUnresolvedType(t, true);
return null;
}
@Override
public boolean setValidator(Predicate<JSType> validator) {
// If the type is already resolved, we can validate it now. If
// the type has not been resolved yet, we need to wait till its
// resolved before we can validate it.
if (this.isResolved()) {
return super.setValidator(validator);
} else {
this.validator = validator;
return true;
}
}
/** Store enough information to define a property at a later time. */
private static final class PropertyContinuation {
private final String propertyName;
private final JSType type;
private final boolean inferred;
private final boolean inExterns;
private final Node propertyNode;
private PropertyContinuation(
String propertyName,
JSType type,
boolean inferred,
boolean inExterns,
Node propertyNode) {
this.propertyName = propertyName;
this.type = type;
this.inferred = inferred;
this.inExterns = inExterns;
this.propertyNode = propertyNode;
}
void commit(ObjectType target) {
target.defineProperty(
propertyName, type, inferred, inExterns, propertyNode);
}
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* <p>The syntactic scope creator scans the parse tree to create a Scope object
* containing all the variable declarations in that scope.</p>
*
* <p>This implementation is not thread-safe.</p>
*
*/
class SyntacticScopeCreator implements ScopeCreator {
private final AbstractCompiler compiler;
private Scope scope;
private String sourceName;
private final RedeclarationHandler redeclarationHandler;
// The arguments variable is special, in that it's declared in every local
// scope, but not explicitly declared.
private static final String ARGUMENTS = "arguments";
public static final DiagnosticType VAR_MULTIPLY_DECLARED_ERROR =
DiagnosticType.error(
"JSC_VAR_MULTIPLY_DECLARED_ERROR",
"Variable {0} first declared in {1}");
public static final DiagnosticType VAR_ARGUMENTS_SHADOWED_ERROR =
DiagnosticType.error(
"JSC_VAR_ARGUMENTS_SHADOWED_ERROR",
"Shadowing \"arguments\" is not allowed");
/**
* Creates a ScopeCreator.
*/
SyntacticScopeCreator(AbstractCompiler compiler) {
this.compiler = compiler;
this.redeclarationHandler = new DefaultRedeclarationHandler();
}
SyntacticScopeCreator(
AbstractCompiler compiler, RedeclarationHandler redeclarationHandler) {
this.compiler = compiler;
this.redeclarationHandler = redeclarationHandler;
}
public Scope createScope(Node n, Scope parent) {
sourceName = null;
if (parent == null) {
scope = new Scope(n, compiler);
} else {
scope = new Scope(parent, n);
}
scanRoot(n, parent);
sourceName = null;
Scope returnedScope = scope;
scope = null;
return returnedScope;
}
private void scanRoot(Node n, Scope parent) {
if (n.getType() == Token.FUNCTION) {
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
final Node fnNameNode = n.getFirstChild();
final Node args = fnNameNode.getNext();
final Node body = args.getNext();
// Bleed the function name into the scope, if it hasn't
//
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> been declared in the outer scope.
String fnName = fnNameNode.getString();
if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
declareVar(fnNameNode);
}
// Args: Declare function variables
Preconditions.checkState(args.getType() == Token.LP);
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.getType() == Token.NAME);
declareVar(a);
}
// Body
scanVars(body, n);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n, null);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n, Node parent) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
declareVar(child);
child = next;
}
return;
case Token.FUNCTION:
if (NodeUtil.isFunctionExpression(n)) {
return;
}
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(n.getFirstChild());
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 2);
Preconditions.checkState(n.getFirstChild().getType() == Token.NAME);
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext();
declareVar(var);
scanVars(block, n);
return; // only one child to scan
case Token.SCRIPT:
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
scanVars(child, n);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Scope s, String name, Node n, CompilerInput input);
}
/**
* The default handler for duplicate declarations.
*/
private class DefaultRedeclarationHandler implements RedeclarationHandler {
public void onRedeclaration(
Scope s, String name, Node n, CompilerInput input) {
Node parent = n.getParent();
// Don't allow multiple variables to be declared at the top level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar(name
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>);
Node origParent = origVar.getParentNode();
if (origParent.getType() == Token.CATCH &&
parent.getType() == Token.CATCH) {
// Okay, both are 'catch(x)' variables.
return;
}
boolean allowDupe = false;
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
info = origVar.nameNode.getJSDocInfo();
if (info == null) {
info = origParent.getJSDocInfo();
}
allowDupe |=
info != null && info.getSuppressions().contains("duplicate");
if (!allowDupe) {
compiler.report(
JSError.make(sourceName, n,
VAR_MULTIPLY_DECLARED_ERROR,
name,
(origVar.input != null
? origVar.input.getName()
: "??")));
}
} else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) {
// Disallow shadowing "arguments" as we can't handle with our current
// scope modeling.
compiler.report(
JSError.make(sourceName, n,
VAR_ARGUMENTS_SHADOWED_ERROR));
}
}
}
/**
* Declares a variable.
*
* @param n The node corresponding to the variable name.
*/
private void declareVar(Node n) {
Preconditions.checkState(n.getType() == Token.NAME);
CompilerInput input = compiler.getInput(sourceName);
String name = n.getString();
if (scope.isDeclared(name, false)
|| (scope.isLocal() && name.equals(ARGUMENTS))) {
redeclarationHandler.onRedeclaration(
scope, name, n, input);
} else {
scope.declare(name, n, null, input);
}
}
/**
* Generates an untyped global scope from the root of AST of compiler (which
* includes externs).
*
* @param compiler The compiler for which the scope is generated.
* @return The new untyped global scope generated as a result of this call.
*/
static Scope generateUntypedTopScope(AbstractCompiler compiler) {
return new SyntacticScopeCreator(compiler).createScope(compiler.getRoot(),
null);
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS><L> out;
/**
* Private constructor. No other classes should create new states.
*
* @param inState Input.
* @param outState Output.
*/
private BranchedFlowState(L inState, List<L> outState) {
Preconditions.checkNotNull(inState);
Preconditions.checkNotNull(outState);
this.in = inState;
this.out = outState;
}
L getIn() {
return in;
}
void setIn(L in) {
Preconditions.checkNotNull(in);
this.in = in;
}
List<L> getOut() {
return out;
}
void setOut(List<L> out) {
Preconditions.checkNotNull(out);
for (L item : out) {
Preconditions.checkNotNull(item);
}
this.out = out;
}
@Override
public String toString() {
return String.format("IN: %s OUT: %s", in, out);
}
@Override
public int hashCode() {
return Objects.hashCode(in, out);
}
}
/**
* Compute set of escaped variables. When a variable is escaped in a
* dataflow analysis, it can be reference outside of the code that we are
* analyzing. A variable is escaped if any of the following is true:
*
* <p><ol>
* <li>It is defined as the exception name in CATCH clause so it became a
* variable local not to our definition of scope.</li>
* <li>Exported variables as they can be needed after the script terminates.
* </li>
* <li>Names of named functions because in javascript, <i>function foo(){}</i>
* does not kill <i>foo</i> in the dataflow.</li>
*/
static void computeEscaped(final Scope jsScope, final Set<Var> escaped,
AbstractCompiler compiler) {
// TODO(user): Very good place to store this information somewhere.
AbstractPostOrderCallback finder = new AbstractPostOrderCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (jsScope == t.getScope() || !NodeUtil.isName(n)
|| NodeUtil.isFunction(parent)) {
return;
}
String name = n.getString();
Var var = t.getScope().getVar(name);
if (var != null && var.scope == jsScope) {
escaped.add(jsScope.getVar(name));
}
}
};
NodeTraversal t = new NodeTraversal(compiler, finder);
t.traverseAtScope(jsScope);
// 1: Remove the exception name in CATCH which technically isn't local to
// begin with.
for (Iterator<Var> i = jsScope.getVars(); i.hasNext();) {
Var var = i.next();
if (var.getParentNode().getType() == Token.CATCH ||
compiler.getCodingConvention().isExported(var.getName())) {
escaped.add(var);
}
}
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
}
}
public void exitScope(NodeTraversal t) {
scopes.pop();
}
/** Returns the current scope at this point in the file. */
protected StaticScope<T> getScope() {
return scopes.peek();
}
}
/**
* Finds all properties defined in the externs file and sets them as
* ineligible for renaming from the type on which they are defined.
*/
private class FindExternProperties extends AbstractScopingCallback {
@Override public void visit(NodeTraversal t, Node n, Node parent) {
// TODO(johnlenz): Support object-literal property definitions.
if (n.getType() == Token.GETPROP) {
String field = n.getLastChild().getString();
T type = typeSystem.getType(getScope(), n.getFirstChild(), field);
Property prop = getProperty(field);
if (typeSystem.isInvalidatingType(type)) {
prop.invalidate();
} else {
prop.addTypeToSkip(type);
// If this is a prototype property, then we want to skip assignments
// to the instance type as well. These assignments are not usually
// seen in the extern code itself, so we must handle them here.
if ((type = typeSystem.getInstanceFromPrototype(type)) != null) {
prop.getTypes().add(type);
prop.typesToSkip.add(type);
}
}
}
}
}
/**
* Traverses the tree, building a map from field names to Nodes for all
* fields that can be renamed.
*/
private class FindRenameableProperties extends AbstractScopingCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.GETPROP) {
handleGetProp(t, n);
} else if (n.getType() == Token.OBJECTLIT) {
handleObjectLit(t, n);
}
}
/**
* Processes a GETPROP node.
*/
private void handleGetProp(NodeTraversal t, Node n) {
String name = n.getLastChild().getString();
T type = typeSystem.getType(getScope(), n.getFirstChild(), name);
Property prop = getProperty(name);
if (!prop.scheduleRenaming(n.getLastChild(),
processProperty(t, prop, type, null))) {
if (showInvalidationWarnings) {
compiler.report(JSError.make(
t.getSourceName(), n, Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()), n.toString()));
}
}
}
/**
* Processes a OBJECTLIT node.
*/
private void handleObjectLit(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
while (child != null) {
// Maybe STRING, GET, SET
// We should never see a mix of numbers and strings.
String name = child.getString();
T type = typeSystem.getType(getScope(), n, name);
Property prop = getProperty(name);
if (!prop.scheduleRenaming(child,
processProperty(t, prop, type, null))) {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
if (showInvalidationWarnings) {
compiler.report(JSError.make(
t.getSourceName(), child, Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()), n.toString()));
}
}
child = child.getNext();
}
}
/**
* Processes a property, adding it to the list of properties to rename.
* @return a representative type for the property reference, which will be
* the highest type on the prototype chain of the provided type. In the
* case of a union type, it will be the highest type on the prototype
* chain of one of the members of the union.
*/
private T processProperty(
NodeTraversal t, Property prop, T type, T relatedType) {
type = typeSystem.restrictByNotNullOrUndefined(type);
if (prop.skipRenaming || typeSystem.isInvalidatingType(type)) {
return null;
}
Iterable<T> alternatives = typeSystem.getTypeAlternatives(type);
if (alternatives != null) {
T firstType = relatedType;
for (T subType : alternatives) {
T lastType = processProperty(t, prop, subType, firstType);
if (lastType != null) {
firstType = firstType == null ? lastType : firstType;
}
}
return firstType;
} else {
T topType = typeSystem.getTypeWithProperty(prop.name, type);
if (typeSystem.isInvalidatingType(topType)) {
return null;
}
prop.addType(type, topType, relatedType);
return topType;
}
}
}
/** Renames all properties with references on more than one type. */
void renameProperties() {
int propsRenamed = 0, propsSkipped = 0, instancesRenamed = 0,
instancesSkipped = 0, singleTypeProps = 0;
for (Property prop : properties.values()) {
if (prop.shouldRename()) {
Map<T, String> propNames = buildPropNames(prop.getTypes(), prop.name);
++propsRenamed;
prop.expandTypesToSkip();
UnionFind<T> types = prop.getTypes();
for (Node node : prop.renameNodes) {
T rootType = prop.rootTypes.get(node);
if (prop.shouldRename(rootType)) {
String newName = propNames.get(rootType);
node.setString(newName);
compiler.reportCodeChange();
++instancesRenamed;
} else {
++instancesSkipped;
}
}
} else {
if (prop.skipRenaming) {
++propsSkipped;
} else {
++singleTypeProps;
}
}
}
logger.info("Renamed " + instancesRenamed + " instances of "
+ propsRenamed + " properties.");
logger.info("Skipped renaming " + instancesSkipped + " invalidated "
+ "properties, " + propsSkipped + " instances of properties "
+ "that were skipped for specific types and " + singleTypeProps
+ " properties that were referenced from only one type.");
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> }
/**
* Chooses a name to use for renaming in each equivalence class and maps
* each type in that class to it.
*/
private Map<T, String> buildPropNames(UnionFind<T> types, String name) {
Map<T, String> names = Maps.newHashMap();
for (Set<T> set : types.allEquivalenceClasses()) {
checkState(!set.isEmpty());
String typeName = null;
for (T type : set) {
if (typeName == null || type.toString().compareTo(typeName) < 0) {
typeName = type.toString();
}
}
String newName;
if ("{...}".equals(typeName)) {
newName = name;
} else {
newName = typeName.replaceAll("[^\\w$]", "_") + "$" + name;
}
for (T type : set) {
names.put(type, newName);
}
}
return names;
}
/** Returns a map from field name to types for which it will be renamed. */
Multimap<String, Collection<T>> getRenamedTypesForTesting() {
Multimap<String, Collection<T>> ret = HashMultimap.create();
for (Map.Entry<String, Property> entry: properties.entrySet()) {
Property prop = entry.getValue();
if (!prop.skipRenaming) {
for (Collection<T> c : prop.getTypes().allEquivalenceClasses()) {
if (!c.isEmpty() && !prop.typesToSkip.contains(c.iterator().next())) {
ret.put(entry.getKey(), c);
}
}
}
}
return ret;
}
/** Interface for providing the type information needed by this pass. */
private interface TypeSystem<T> {
// TODO(user): add a getUniqueName(T type) method that is guaranteed
// to be unique, performant and human-readable.
/** Returns the top-most scope used by the type system (if any). */
StaticScope<T> getRootScope();
/** Returns the new scope started at the given function node. */
StaticScope<T> getFunctionScope(Node node);
/**
* Returns the type of the given node.
* @param prop Only types with this property need to be returned. In general
* with type tightening, this will require no special processing, but in
* the case of an unknown JSType, we might need to add in the native
* types since we don't track them, but only if they have the given
* property.
*/
T getType(StaticScope<T> scope, Node node, String prop);
/**
* Returns true if a field reference on this type will invalidiate all
* references to that field as candidates for renaming. This is true if the
* type is unknown or all-inclusive, as variables with such a type could be
* references to any object.
*/
boolean isInvalidatingType(T type);
/**
* Informs the given type system that a type is invalidating due to a type
* mismatch found during type checking.
*/
void addInvalidatingType(JSType type);
/**
* Returns a set of
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> types that should be skipped given the given type.
* This is necessary for interfaces when using JSTypes, as all super
* interfaces must also be skipped.
*/
ImmutableSet<T> getTypesToSkipForType(T type);
/**
* Determines whether the given type is one whose properties should not be
* considered for renaming.
*/
boolean isTypeToSkip(T type);
/** Remove null and undefined from the options in the given type. */
T restrictByNotNullOrUndefined(T type);
/**
* Returns the alternatives if this is a type that represents multiple
* types, and null if not. Union and interface types can correspond to
* multiple other types.
*/
Iterable<T> getTypeAlternatives(T type);
/**
* Returns the type in the chain from the given type that contains the given
* field or null if it is not found anywhere.
*/
T getTypeWithProperty(String field, T type);
/**
* Returns the type of the instance of which this is the prototype or null
* if this is not a function prototype.
*/
T getInstanceFromPrototype(T type);
/**
* Records that this property could be referenced from any interface that
* this type, or any type in its superclass chain, implements.
*/
void recordInterfaces(T type, T relatedType,
DisambiguateProperties<T>.Property p);
}
/** Implementation of TypeSystem using JSTypes. */
private static class JSTypeSystem implements TypeSystem<JSType> {
private final Set<JSType> invalidatingTypes;
private JSTypeRegistry registry;
public JSTypeSystem(AbstractCompiler compiler) {
registry = compiler.getTypeRegistry();
invalidatingTypes = Sets.newHashSet(
registry.getNativeType(JSTypeNative.ALL_TYPE),
registry.getNativeType(JSTypeNative.NO_OBJECT_TYPE),
registry.getNativeType(JSTypeNative.NO_TYPE),
registry.getNativeType(JSTypeNative.FUNCTION_PROTOTYPE),
registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
registry.getNativeType(JSTypeNative.OBJECT_PROTOTYPE),
registry.getNativeType(JSTypeNative.TOP_LEVEL_PROTOTYPE),
registry.getNativeType(JSTypeNative.UNKNOWN_TYPE));
}
@Override public void addInvalidatingType(JSType type) {
checkState(!type.isUnionType());
invalidatingTypes.add(type);
}
@Override public StaticScope<JSType> getRootScope() { return null; }
@Override public StaticScope<JSType> getFunctionScope(Node node) {
return null;
}
@Override public JSType getType(
StaticScope<JSType> scope, Node node, String prop) {
if (node.getJSType() == null) {
return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
return node.getJSType();
}
@Override public boolean isInvalidatingType(JSType type) {
if (type == null || invalidatingTypes.contains(type) ||
type.isUnknownType() /* unresolved types */) {
return true;
}
ObjectType
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> this.tt = tt;
this.codingConvention = convention;
}
@Override public void addInvalidatingType(JSType type) {
checkState(!type.isUnionType());
invalidatingTypes.add(type);
}
@Override public StaticScope<ConcreteType> getRootScope() {
return tt.getTopScope();
}
@Override public StaticScope<ConcreteType> getFunctionScope(Node decl) {
ConcreteFunctionType func = tt.getConcreteFunction(decl);
return (func != null) ?
func.getScope() : (StaticScope<ConcreteType>) null;
}
@Override
public ConcreteType getType(
StaticScope<ConcreteType> scope, Node node, String prop) {
if (scope != null) {
ConcreteType c = tt.inferConcreteType(
(TightenTypes.ConcreteScope) scope, node);
return maybeAddAutoboxes(c, node, prop);
} else {
return null;
}
}
/**
* Add concrete types for autoboxing types if necessary. The concrete type
* system does not track native types, like string, so add them if they are
* present in the JSType for the node.
*/
private ConcreteType maybeAddAutoboxes(
ConcreteType cType, Node node, String prop) {
JSType jsType = node.getJSType();
if (jsType == null) {
return cType;
} else if (jsType.isUnknownType()) {
for (JSTypeNative nativeType : nativeTypes) {
ConcreteType concrete = tt.getConcreteInstance(
tt.getTypeRegistry().getNativeObjectType(nativeType));
if (concrete != null && !concrete.getPropertyType(prop).isNone()) {
cType = cType.unionWith(concrete);
}
}
return cType;
}
return maybeAddAutoboxes(cType, jsType, prop);
}
private ConcreteType maybeAddAutoboxes(
ConcreteType cType, JSType jsType, String prop) {
jsType = jsType.restrictByNotNullOrUndefined();
if (jsType instanceof UnionType) {
for (JSType alt : ((UnionType) jsType).getAlternates()) {
return maybeAddAutoboxes(cType, alt, prop);
}
}
if (jsType.autoboxesTo() != null) {
JSType autoboxed = jsType.autoboxesTo();
return cType.unionWith(tt.getConcreteInstance((ObjectType) autoboxed));
} else if (jsType.unboxesTo() != null) {
return cType.unionWith(tt.getConcreteInstance((ObjectType) jsType));
}
return cType;
}
@Override public boolean isInvalidatingType(ConcreteType type) {
// We will disallow types on functions so that 'prototype' is not renamed.
// TODO(user): Support properties on functions as well.
return (type == null) || type.isAll() || type.isFunction()
|| (type.isInstance()
&& invalidatingTypes.contains(type.toInstance().instanceType));
}
@Override
public ImmutableSet<ConcreteType> getTypesToSkipForType(ConcreteType type)
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> boolean hasReferenceName() {
return referencedObjType == null ?
null : referencedObjType.hasReferenceName();
}
@Override public boolean matchesNumberContext() {
return referencedType.matchesNumberContext();
}
@Override
public boolean matchesStringContext() {
return referencedType.matchesStringContext();
}
@Override public boolean matchesObjectContext() {
return referencedType.matchesObjectContext();
}
@Override
public boolean canBeCalled() {
return referencedType.canBeCalled();
}
@Override
public boolean isNoType() {
return referencedType.isNoType();
}
@Override
public boolean isNoObjectType() {
return referencedType.isNoObjectType();
}
@Override
public boolean isNoResolvedType() {
return referencedType.isNoResolvedType();
}
@Override
public boolean isUnknownType() {
return referencedType.isUnknownType();
}
@Override
public boolean isCheckedUnknownType() {
return referencedType.isCheckedUnknownType();
}
@Override
public boolean isNullable() {
return referencedType.isNullable();
}
@Override
public boolean isFunctionPrototypeType() {
return referencedType.isFunctionPrototypeType();
}
@Override
public boolean isEnumType() {
return referencedType.isEnumType();
}
@Override
public boolean isEnumElementType() {
return referencedType.isEnumElementType();
}
@Override
public boolean isConstructor() {
return referencedType.isConstructor();
}
@Override
public boolean isNominalType() {
return referencedType.isNominalType();
}
@Override
public boolean isInstanceType() {
return referencedType.isInstanceType();
}
@Override
public boolean isInterface() {
return referencedType.isInterface();
}
@Override
public boolean isOrdinaryFunction() {
return referencedType.isOrdinaryFunction();
}
@Override
public TernaryValue testForEquality(JSType that) {
return referencedType.testForEquality(that);
}
@Override
public boolean isSubtype(JSType that) {
return referencedType.isSubtype(that);
}
@Override
public Iterable<ObjectType> getCtorImplementedInterfaces() {
return referencedObjType == null ? Collections.<ObjectType>emptyList() :
referencedObjType.getCtorImplementedInterfaces();
}
@Override
public boolean canAssignTo(JSType that) {
return referencedType.canAssignTo(that);
}
@Override
public boolean isEquivalentTo(JSType that) {
if (this == that) {
return true;
}
return referencedType.isEquivalentTo(that);
}
@Override
public int hashCode() {
return referencedType.hashCode();
}
@Override
public String toString() {
return referencedType.toString();
}
@Override
public ObjectType getImplicitPrototype() {
return referencedObjType == null ? null :
referencedObjType.getImplicitPrototype();
}
@Override
boolean defineProperty(String propertyName, JSType type,
boolean inferred, boolean inExterns, Node propertyNode) {
return referencedObjType == null ? true :
referencedObjType.defineProperty(
propertyName, type, inferred, inExterns,
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> DiagnosticType.disabled(
"JSC_DEPRECATED_CLASS_REASON",
"Class {0} has been deprecated: {1}");
static final DiagnosticType BAD_PRIVATE_GLOBAL_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PRIVATE_GLOBAL_ACCESS",
"Access to private variable {0} not allowed outside file {1}.");
static final DiagnosticType BAD_PRIVATE_PROPERTY_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PRIVATE_PROPERTY_ACCESS",
"Access to private property {0} of {1} not allowed here.");
static final DiagnosticType BAD_PROTECTED_PROPERTY_ACCESS =
DiagnosticType.disabled(
"JSC_BAD_PROTECTED_PROPERTY_ACCESS",
"Access to protected property {0} of {1} not allowed here.");
static final DiagnosticType PRIVATE_OVERRIDE =
DiagnosticType.disabled(
"JSC_PRIVATE_OVERRIDE",
"Overriding private property of {0}.");
static final DiagnosticType VISIBILITY_MISMATCH =
DiagnosticType.disabled(
"JSC_VISIBILITY_MISMATCH",
"Overriding {0} property of {1} with {2} property.");
static final DiagnosticType CONST_PROPERTY_REASSIGNED_VALUE =
DiagnosticType.warning(
"JSC_CONSTANT_PROPERTY_REASSIGNED_VALUE",
"constant property {0} assigned a value more than once");
private final AbstractCompiler compiler;
private final TypeValidator validator;
// State about the current traversal.
private int deprecatedDepth = 0;
private int methodDepth = 0;
private JSType currentClass = null;
private final Multimap<String, String> initializedConstantProperties;
CheckAccessControls(AbstractCompiler compiler) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.initializedConstantProperties = HashMultimap.create();
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void hotSwapScript(Node scriptRoot) {
NodeTraversal.traverse(compiler, scriptRoot, this);
}
public void enterScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
Node parent = n.getParent();
if (isDeprecatedFunction(n, parent)) {
deprecatedDepth++;
}
if (methodDepth == 0) {
currentClass = getClassOfMethod(n, parent);
}
methodDepth++;
}
}
public void exitScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
Node parent = n.getParent();
if (isDeprecatedFunction(n, parent)) {
deprecatedDepth--;
}
methodDepth--;
if (methodDepth == 0) {
currentClass = null;
}
}
}
/**
* Gets the type of the class that "owns" a method, or null if
* we know that its un-owned.
*/
private JSType getClassOfMethod(Node n, Node parent) {
if (parent.getType() == Token.ASSIGN) {
Node
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> lValue = parent.getFirstChild();
if (lValue.isQualifiedName()) {
if (lValue.getType() == Token.GETPROP) {
// We have an assignment of the form "a.b = ...".
JSType lValueType = lValue.getJSType();
if (lValueType != null && lValueType.isConstructor()) {
// If a.b is a constructor, then everything in this function
// belongs to the "a.b" type.
return ((FunctionType) lValueType).getInstanceType();
} else {
// If a.b is not a constructor, then treat this as a method
// of whatever type is on "a".
return normalizeClassType(lValue.getFirstChild().getJSType());
}
} else {
// We have an assignment of the form "a = ...", so pull the
// type off the "a".
return normalizeClassType(lValue.getJSType());
}
}
} else if (NodeUtil.isFunctionDeclaration(n) ||
parent.getType() == Token.NAME) {
return normalizeClassType(n.getJSType());
}
return null;
}
/**
* Normalize the type of a constructor, its instance, and its prototype
* all down to the same type (the instance type).
*/
private JSType normalizeClassType(JSType type) {
if (type == null || type.isUnknownType()) {
return type;
} else if (type.isConstructor()) {
return ((FunctionType) type).getInstanceType();
} else if (type.isFunctionPrototypeType()) {
FunctionType owner = ((FunctionPrototypeType) type).getOwnerFunction();
if (owner.isConstructor()) {
return owner.getInstanceType();
}
}
return type;
}
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
checkNameDeprecation(t, n, parent);
checkNameVisibility(t, n, parent);
break;
case Token.GETPROP:
checkPropertyDeprecation(t, n, parent);
checkPropertyVisibility(t, n, parent);
checkConstantProperty(t, n);
break;
case Token.NEW:
checkConstructorDeprecation(t, n, parent);
break;
}
}
/**
* Checks the given NEW node to ensure that access restrictions are obeyed.
*/
private void checkConstructorDeprecation(NodeTraversal t, Node n,
Node parent) {
JSType type = n.getJSType();
if (type != null) {
String deprecationInfo = getTypeDeprecationInfo(type);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_CLASS_REASON,
type.toString(), deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_CLASS, type.toString()));
}
}
}
}
/**
* Checks the given NAME
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> node to ensure that access restrictions are obeyed.
*/
private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking definitions or constructors.
if (parent.getType() == Token.FUNCTION || parent.getType() == Token.VAR ||
parent.getType() == Token.NEW) {
return;
}
Scope.Var var = t.getScope().getVar(n.getString());
JSDocInfo docInfo = var == null ? null : var.getJSDocInfo();
if (docInfo != null && docInfo.isDeprecated() &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (docInfo.getDeprecationReason() != null) {
compiler.report(
t.makeError(n, DEPRECATED_NAME_REASON, n.getString(),
docInfo.getDeprecationReason()));
} else {
compiler.report(
t.makeError(n, DEPRECATED_NAME, n.getString()));
}
}
}
/**
* Checks the given GETPROP node to ensure that access restrictions are
* obeyed.
*/
private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking constructors.
if (parent.getType() == Token.NEW) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(n.getFirstChild().getJSType()));
String propertyName = n.getLastChild().getString();
if (objectType != null) {
String deprecationInfo
= getPropertyDeprecationInfo(objectType, propertyName);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_PROP_REASON, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true),
deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_PROP, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true)));
}
}
}
}
/**
* Determines whether the given name is visible in the current context.
* @param t The current traversal.
* @param name The name node.
*/
private void checkNameVisibility(NodeTraversal t, Node name, Node parent) {
Var var = t.getScope().getVar(name.getString());
if (var != null) {
JSDocInfo docInfo = var.getJSDocInfo();
if (docInfo != null) {
// If a name is private, make sure that we're in the same file.
Visibility visibility = docInfo.getVisibility();
if (visibility == Visibility.PRIVATE &&
!t.getInput().getName().equals(docInfo.getSourceName())) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
compiler.report(
t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS,
name.getString(), docInfo.getSourceName()));
}
}
}
}
/**
* Determines whether the given property with @const tag got re
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>assigned
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkConstantProperty(NodeTraversal t,
Node getprop) {
// Check whether the property is modified
Node parent = getprop.getParent();
if (!(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == getprop)
&& (parent.getType() != Token.INC) && (parent.getType() != Token.DEC)) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
// Check whether constant properties are reassigned
if (objectType != null) {
ObjectType oType = objectType;
while (oType != null) {
if (oType.hasReferenceName()) {
if (initializedConstantProperties.containsEntry(
oType.getReferenceName(), propertyName)) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_REASSIGNED_VALUE,
propertyName));
break;
}
}
oType = oType.getImplicitPrototype();
}
JSDocInfo info = objectType.getOwnPropertyJSDocInfo(propertyName);
if (info != null && info.isConstant()
&& objectType.hasReferenceName()) {
initializedConstantProperties.put(objectType.getReferenceName(),
propertyName);
}
// Add the prototype when we're looking at an instance object
if (objectType.isInstanceType()) {
ObjectType prototype = objectType.getImplicitPrototype();
if (prototype != null) {
JSDocInfo prototypeInfo
= prototype.getOwnPropertyJSDocInfo(propertyName);
if (prototypeInfo != null && prototypeInfo.isConstant()
&& prototype.hasReferenceName()) {
initializedConstantProperties.put(prototype.getReferenceName(),
propertyName);
}
}
}
}
}
/**
* Determines whether the given property is visible in the current context.
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkPropertyVisibility(NodeTraversal t,
Node getprop, Node parent) {
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
if (objectType != null) {
// Is this a normal property access, or are we trying to override
// an existing property?
boolean isOverride = parent.getJSDocInfo() != null &&
parent.getType() == Token.ASSIGN &&
parent.getFirstChild() == getprop;
// Find the lowest property defined on a class with visibility
// information.
if (isOverride) {
objectType = objectType.getImplicitPrototype();
}
JSDocInfo docInfo = null;
for (; objectType != null;
objectType = objectType.getImplicitPrototype()) {
docInfo = objectType.getOwnPropertyJSDocInfo(propertyName);
if (docInfo != null &&
docInfo.getVisibility() != Visibility.INHERITED) {
break;
}
}
if (objectType == null) {
// We couldn
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>'t find a visibility modifier; assume it's public.
return;
}
boolean sameInput =
t.getInput().getName().equals(docInfo.getSourceName());
Visibility visibility = docInfo.getVisibility();
JSType ownerType = normalizeClassType(objectType);
if (isOverride) {
// Check an ASSIGN statement that's trying to override a property
// on a superclass.
JSDocInfo overridingInfo = parent.getJSDocInfo();
Visibility overridingVisibility = overridingInfo == null ?
Visibility.INHERITED : overridingInfo.getVisibility();
// Check that (a) the property *can* be overridden, and
// (b) that the visibility of the override is the same as the
// visibility of the original property.
if (visibility == Visibility.PRIVATE && !sameInput) {
compiler.report(
t.makeError(getprop, PRIVATE_OVERRIDE,
objectType.toString()));
} else if (overridingVisibility != Visibility.INHERITED &&
overridingVisibility != visibility) {
compiler.report(
t.makeError(getprop, VISIBILITY_MISMATCH,
visibility.name(), objectType.toString(),
overridingVisibility.name()));
}
} else {
if (sameInput) {
// private access is always allowed in the same file.
return;
} else if (visibility == Visibility.PRIVATE &&
(currentClass == null || ownerType.differsFrom(currentClass))) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
// private access is not allowed outside the file from a different
// enclosing class.
compiler.report(
t.makeError(getprop,
BAD_PRIVATE_PROPERTY_ACCESS,
propertyName,
validator.getReadableJSTypeName(
getprop.getFirstChild(), true)));
} else if (visibility == Visibility.PROTECTED) {
// There are 3 types of legal accesses of a protected property:
// 1) Accesses in the same file
// 2) Overriding the property in a subclass
// 3) Accessing the property from inside a subclass
// The first two have already been checked for.
if (currentClass == null || !currentClass.isSubtype(ownerType)) {
compiler.report(
t.makeError(getprop, BAD_PROTECTED_PROPERTY_ACCESS,
propertyName,
validator.getReadableJSTypeName(
getprop.getFirstChild(), true)));
}
}
}
}
}
/**
* Whether the given access of a private constructor is legal.
*
* For example,
* new PrivateCtor_(); // not legal
* PrivateCtor_.newInstance(); // legal
* x instanceof PrivateCtor_ // legal
*
* This is a weird special case, because our visibility system is inherited
* from Java, and JavaScript has no distinction between classes and
* constructors like Java does.
*
* We may want to revisit this if we decide to make the restrictions tighter.
*/
private static boolean isValidPrivateConstructorAccess(Node parent) {
return parent.getType() != Token.NEW;
}
/**
* Determines whether a deprecation warning should be emitted.
* @param t
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> The current traversal.
* @param n The node which we are checking.
* @param parent The parent of the node which we are checking.
*/
private boolean shouldEmitDeprecationWarning(
NodeTraversal t, Node n, Node parent) {
// In the global scope, there are only two kinds of accesses that should
// be flagged for warnings:
// 1) Calls of deprecated functions and methods.
// 2) Instantiations of deprecated classes.
// For now, we just let everything else by.
if (t.inGlobalScope()) {
if (!((parent.getType() == Token.CALL && parent.getFirstChild() == n) ||
n.getType() == Token.NEW)) {
return false;
}
}
// We can always assign to a deprecated property, to keep it up to date.
if (n.getType() == Token.GETPROP && n == parent.getFirstChild() &&
NodeUtil.isAssignmentOp(parent)) {
return false;
}
return !canAccessDeprecatedTypes(t);
}
/**
* Returns whether it's currently ok to access deprecated names and
* properties.
*
* There are 3 exceptions when we're allowed to use a deprecated
* type or property:
* 1) When we're in a deprecated function.
* 2) When we're in a deprecated class.
* 3) When we're in a static method of a deprecated class.
*/
private boolean canAccessDeprecatedTypes(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
Node scopeRootParent = scopeRoot.getParent();
return
// Case #1
(deprecatedDepth > 0) ||
// Case #2
(getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) ||
// Case #3
(scopeRootParent != null && scopeRootParent.getType() == Token.ASSIGN &&
getTypeDeprecationInfo(
getClassOfMethod(scopeRoot, scopeRootParent)) != null);
}
/**
* Returns whether this is a function node annotated as deprecated.
*/
private static boolean isDeprecatedFunction(Node n, Node parent) {
if (n.getType() == Token.FUNCTION) {
JSType type = n.getJSType();
if (type != null) {
return getTypeDeprecationInfo(type) != null;
}
}
return false;
}
/**
* Returns the deprecation reason for the type if it is marked
* as being deprecated. Returns empty string if the type is deprecated
* but no reason was given. Returns null if the type is not deprecated.
*/
private static String getTypeDeprecationInfo(JSType type) {
if (type == null) {
return null;
}
JSDocInfo info = type.getJSDocInfo();
if (info != null && info.isDeprecated()) {
if (info.getDeprecationReason() != null) {
return info.getDeprecationReason();
}
return "";
}
ObjectType objType = ObjectType.cast(type);
if (objType != null) {
ObjectType implicitProto = objType.getImplicitPrototype();
if (implicitProto != null) {
return getTypeDeprecation
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> an instrumentation template. */
private final PassFactory instrumentFunctions =
new PassFactory("instrumentFunctions", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new CompilerPass() {
@Override public void process(Node externs, Node root) {
try {
FileReader templateFile =
new FileReader(options.instrumentationTemplate);
(new InstrumentFunctions(
compiler, functionNames,
options.instrumentationTemplate,
options.appNameStr,
templateFile)).process(externs, root);
} catch (IOException e) {
compiler.report(
JSError.make(AbstractCompiler.READ_ERROR,
options.instrumentationTemplate));
}
}
};
}
};
/**
* Create a no-op pass that can only run once. Used to break up loops.
*/
private static PassFactory createEmptyPass(String name) {
return new PassFactory(name, true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return runInSerial();
}
};
}
/**
* Runs custom passes that are designated to run at a particular time.
*/
private PassFactory getCustomPasses(
final CustomPassExecutionTime executionTime) {
return new PassFactory("runCustomPasses", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return runInSerial(options.customPasses.get(executionTime));
}
};
}
/**
* All inlining is forbidden in heuristic renaming mode, because inlining
* will ruin the invariants that it depends on.
*/
private boolean isInliningForbidden() {
return options.propertyRenaming == PropertyRenamingPolicy.HEURISTIC ||
options.propertyRenaming ==
PropertyRenamingPolicy.AGGRESSIVE_HEURISTIC;
}
/** Create a compiler pass that runs the given passes in serial. */
private static CompilerPass runInSerial(final CompilerPass ... passes) {
return runInSerial(Lists.newArrayList(passes));
}
/** Create a compiler pass that runs the given passes in serial. */
private static CompilerPass runInSerial(
final Collection<CompilerPass> passes) {
return new CompilerPass() {
@Override public void process(Node externs, Node root) {
for (CompilerPass pass : passes) {
pass.process(externs, root);
}
}
};
}
@VisibleForTesting
static Map<String, Node> getAdditionalReplacements(
CompilerOptions options) {
Map<String, Node> additionalReplacements = Maps.newHashMap();
if (options.markAsCompiled || options.closurePass) {
additionalReplacements.put(COMPILED_CONSTANT_NAME, new Node(Token.TRUE));
}
if (options.closurePass && options.locale != null) {
additionalReplacements.put(CLOSURE_LOCALE_CONSTANT_NAME,
Node.newString(options.locale));
}
return additionalReplacements;
}
private final PassFactory printNameReferenceGraph =
new PassFactory("printNameReferenceGraph", true) {
@Override
protected CompilerPass createInternal(final AbstractCompiler compiler) {
return new Compiler
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* A builder for the Rhino Node representing Function parameters.
* @author nicksantos@google.com (Nick Santos)
*/
public class FunctionParamBuilder {
private final JSTypeRegistry registry;
private final Node root = new Node(Token.LP);
public FunctionParamBuilder(JSTypeRegistry registry) {
this.registry = registry;
}
/**
* Add parameters of the given type to the end of the param list.
* @return False if this is called after optional params are added.
*/
public boolean addRequiredParams(JSType ...types) {
if (hasOptionalOrVarArgs()) {
return false;
}
for (JSType type : types) {
newParameter(type);
}
return true;
}
/**
* Add optional parameters of the given type to the end of the param list.
* @param types Types for each optional parameter. The builder will make them
* undefineable.
* @return False if this is called after var args are added.
*/
public boolean addOptionalParams(JSType ...types) {
if (hasVarArgs()) {
return false;
}
for (JSType type
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> : types) {
newParameter(registry.createOptionalType(type)).setOptionalArg(true);
}
return true;
}
/**
* Add variable arguments to the end of the parameter list.
* @return False if this is called after var args are added.
*/
public boolean addVarArgs(JSType type) {
if (hasVarArgs()) {
return false;
}
// There are two types of variable argument functions:
// 1) Programmer-defined var args
// 2) Native bottom types that can accept any argument.
// For the first one, "undefined" is a valid value for all arguments.
// For the second, we do not want to cast it up to undefined.
if (!type.isEmptyType()) {
type = registry.createOptionalType(type);
}
newParameter(type).setVarArgs(true);
return true;
}
/**
* Copies the parameter specification from the given node.
*/
public Node newParameterFromNode(Node n) {
Node newParam = newParameter(n.getJSType());
newParam.setVarArgs(n.isVarArgs());
newParam.setOptionalArg(n.isOptionalArg());
return newParam;
}
// Add a parameter to the list with the given type.
private Node newParameter(JSType type) {
Node paramNode = Node.newString(Token.NAME, "");
paramNode.setJSType(type);
root.addChildToBack(paramNode);
return paramNode;
}
public Node build() {
return root;
}
private boolean hasOptionalOrVarArgs() {
Node lastChild = root.getLastChild();
return lastChild != null &&
(lastChild.isOptionalArg() || lastChild.isVarArgs());
}
public boolean hasVarArgs() {
Node lastChild = root.getLastChild();
return lastChild != null && lastChild.isVarArgs();
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
token = eatTokensUntilEOL(token);
continue retry;
case EXPORT:
if (!jsdocBuilder.recordExport()) {
parser.addParserWarning("msg.jsdoc.export",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case EXTERNS:
if (!jsdocBuilder.recordExterns()) {
parser.addParserWarning("msg.jsdoc.externs",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case JAVA_DISPATCH:
if (!jsdocBuilder.recordJavaDispatch()) {
parser.addParserWarning("msg.jsdoc.javadispatch",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case EXTENDS:
case IMPLEMENTS:
skipEOLs();
token = next();
lineno = stream.getLineno();
charno = stream.getCharno();
boolean matchingRc = false;
if (token == JsDocToken.LC) {
token = next();
matchingRc = true;
}
if (token == JsDocToken.STRING) {
Node typeNode = parseAndRecordTypeNameNode(
token, lineno, charno, matchingRc);
lineno = stream.getLineno();
charno = stream.getCharno();
typeNode = wrapNode(Token.BANG, typeNode);
if (typeNode != null && !matchingRc) {
typeNode.putBooleanProp(Node.BRACELESS_TYPE, true);
}
type = createJSTypeExpression(typeNode);
if (annotation == Annotation.EXTENDS) {
// record the extended type, check later
extendedTypes.add(new ExtendedTypeInfo(
type, stream.getLineno(), stream.getCharno()));
} else {
Preconditions.checkState(
annotation == Annotation.IMPLEMENTS);
if (!jsdocBuilder.recordImplementedInterface(type)) {
parser.addTypeWarning("msg.jsdoc.implements.duplicate",
lineno, charno);
}
}
token = next();
if (matchingRc) {
if (token != JsDocToken.RC) {
parser.addTypeWarning("msg.jsdoc.missing.rc",
stream.getLineno(), stream.getCharno());
}
} else if (token != JsDocToken.EOL &&
token != JsDocToken.EOF && token != JsDocToken.EOC) {
parser.addTypeWarning("msg.end.annotation.expected",
stream.getLineno(), stream.getCharno());
}
} else {
parser.addTypeWarning("msg.no.type.name", lineno, charno);
}
token = eatTokensUntilEOL(token);
continue retry;
case HIDDEN:
if (!jsdocBuilder.recordHiddenness()) {
parser.addParserWarning("msg.jsdoc.hidden",
stream.getLineno(), stream.getCharno());
}
token = eatTokensUntilEOL();
continue retry;
case LENDS:
skipEOLs();
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
} else if (!jsdocBuilder.recordTemplateTypeName(
templateTypeName)) {
parser.addTypeWarning("msg.jsdoc.template.at.most.once",
stream.getLineno(), stream.getCharno());
}
token = templateInfo.token;
continue retry;
case VERSION:
ExtractionInfo versionInfo = extractSingleLineBlock();
String version = versionInfo.string;
if (version.length() == 0) {
parser.addParserWarning("msg.jsdoc.versionmissing",
stream.getLineno(), stream.getCharno());
} else {
if (!jsdocBuilder.recordVersion(version)) {
parser.addParserWarning("msg.jsdoc.extraversion",
stream.getLineno(), stream.getCharno());
}
}
token = versionInfo.token;
continue retry;
case DEFINE:
case RETURN:
case THIS:
case TYPE:
case TYPEDEF:
lineno = stream.getLineno();
charno = stream.getCharno();
Node typeNode = null;
if (!lookAheadForTypeAnnotation() &&
annotation == Annotation.RETURN) {
// If RETURN doesn't have a type annotation, record
// it as the unknown type.
typeNode = newNode(Token.QMARK);
} else {
skipEOLs();
token = next();
typeNode = parseAndRecordTypeNode(token, lineno, charno);
}
if (annotation == Annotation.THIS) {
typeNode = wrapNode(Token.BANG, typeNode);
if (typeNode != null && token != JsDocToken.LC) {
typeNode.putBooleanProp(Node.BRACELESS_TYPE, true);
}
}
type = createJSTypeExpression(typeNode);
if (type == null) {
// error reported during recursive descent
// recovering parsing
} else {
switch (annotation) {
case DEFINE:
if (!jsdocBuilder.recordDefineType(type)) {
parser.addParserWarning("msg.jsdoc.define",
lineno, charno);
}
break;
case RETURN:
if (!jsdocBuilder.recordReturnType(type)) {
parser.addTypeWarning(
"msg.jsdoc.incompat.type", lineno, charno);
break;
}
// Find the return's description (if applicable).
if (jsdocBuilder.shouldParseDocumentation()) {
ExtractionInfo returnDescriptionInfo =
extractMultilineTextualBlock(token);
String returnDescription =
returnDescriptionInfo.string;
if (returnDescription.length() > 0) {
jsdocBuilder.recordReturnDescription(
returnDescription);
}
token = returnDescriptionInfo.token;
} else {
token = eatTokensUntilEOL(token);
}
continue retry;
case THIS:
if (!jsdocBuilder.recordThisType(type)) {
parser.addTypeWarning(
"msg.jsdoc.incompat.type", lineno, charno);
}
break;
case TYPE:
if (!jsdocBuilder.recordType(type)) {
parser.addTypeWarning(
"msg.jsdoc.incompat.type", lineno
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> // compatibility with previous versions of the spec whenever we can.
// We should try to gradually withdraw support for these.
/**
* TypeExpressionAnnotation := TypeExpression |
* '{' TopLevelTypeExpression '}'
*/
private Node parseTypeExpressionAnnotation(JsDocToken token) {
if (token == JsDocToken.LC) {
skipEOLs();
Node typeNode = parseTopLevelTypeExpression(next());
if (typeNode != null) {
skipEOLs();
if (!match(JsDocToken.RC)) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
} else {
next();
}
}
return typeNode;
} else {
return parseTypeExpression(token);
}
}
/**
* ParamTypeExpressionAnnotation :=
* '{' OptionalParameterType '}' |
* '{' TopLevelTypeExpression '}' |
* '{' '...' TopLevelTypeExpression '}'
*
* OptionalParameterType :=
* TopLevelTypeExpression '='
*/
private Node parseParamTypeExpressionAnnotation(JsDocToken token) {
Preconditions.checkArgument(token == JsDocToken.LC);
skipEOLs();
boolean restArg = false;
token = next();
if (token == JsDocToken.ELLIPSIS) {
token = next();
if (token == JsDocToken.RC) {
// EMPTY represents the UNKNOWN type in the Type AST.
return wrapNode(Token.ELLIPSIS, new Node(Token.EMPTY));
}
restArg = true;
}
Node typeNode = parseTopLevelTypeExpression(token);
if (typeNode != null) {
skipEOLs();
if (restArg) {
typeNode = wrapNode(Token.ELLIPSIS, typeNode);
} else if (match(JsDocToken.EQUALS)) {
next();
skipEOLs();
typeNode = wrapNode(Token.EQUALS, typeNode);
}
if (!match(JsDocToken.RC)) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
} else {
next();
}
}
return typeNode;
}
/**
* TypeNameAnnotation := TypeName | '{' TypeName '}'
*/
private Node parseTypeNameAnnotation(JsDocToken token) {
if (token == JsDocToken.LC) {
skipEOLs();
Node typeNode = parseTypeName(next());
if (typeNode != null) {
skipEOLs();
if (!match(JsDocToken.RC)) {
reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
} else {
next();
}
}
return typeNode;
} else {
return parseTypeName(token);
}
}
/**
* TopLevelTypeExpression := TypeExpression
* | TypeUnionList
*
* We made this rule up, for the sake of backwards compatibility.
*/
private Node parseTopLevelTypeExpression(JsDocToken token) {
Node typeExpr = parseTypeExpression(token);
if (typeExpr != null) {
// top-level unions are allowed
if (match(JsDocToken.PIPE)) {
next();
if
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> (match(JsDocToken.PIPE)) {
// We support double pipes for backwards-compatibility.
next();
}
skipEOLs();
token = next();
return parseUnionTypeWithAlternate(token, typeExpr);
}
}
return typeExpr;
}
/**
* TypeExpressionList := TopLevelTypeExpression
* | TopLevelTypeExpression ',' TypeExpressionList
*/
private Node parseTypeExpressionList(JsDocToken token) {
Node typeExpr = parseTopLevelTypeExpression(token);
if (typeExpr == null) {
return null;
}
Node typeList = new Node(Token.BLOCK);
typeList.addChildToBack(typeExpr);
while (match(JsDocToken.COMMA)) {
next();
skipEOLs();
typeExpr = parseTopLevelTypeExpression(next());
if (typeExpr == null) {
return null;
}
typeList.addChildToBack(typeExpr);
}
return typeList;
}
/**
* TypeExpression := BasicTypeExpression
* | '?' BasicTypeExpression
* | '!' BasicTypeExpression
* | BasicTypeExpression '?'
* | BasicTypeExpression '!'
* | '?'
*/
private Node parseTypeExpression(JsDocToken token) {
if (token == JsDocToken.QMARK) {
// A QMARK could mean that a type is nullable, or that it's unknown.
// We use look-ahead 1 to determine whether it's unknown. Otherwise,
// we assume it means nullable. There are 5 cases:
// {?} - right curly
// {?=} - equals
// {function(?, number)} - comma
// {function(number, ?)} - right paren
// {function(): ?|number} - pipe
// I'm not a big fan of using look-ahead for this, but it makes
// the type language a lot nicer.
token = next();
if (token == JsDocToken.COMMA ||
token == JsDocToken.EQUALS ||
token == JsDocToken.RC ||
token == JsDocToken.RP ||
token == JsDocToken.PIPE) {
restoreLookAhead(token);
return newNode(Token.QMARK);
}
return wrapNode(Token.QMARK, parseBasicTypeExpression(token));
} else if (token == JsDocToken.BANG) {
return wrapNode(Token.BANG, parseBasicTypeExpression(next()));
} else {
Node basicTypeExpr = parseBasicTypeExpression(token);
if (basicTypeExpr != null) {
if (match(JsDocToken.QMARK)) {
next();
return wrapNode(Token.QMARK, basicTypeExpr);
} else if (match(JsDocToken.BANG)) {
next();
return wrapNode(Token.BANG, basicTypeExpr);
}
}
return basicTypeExpr;
}
}
/**
* BasicTypeExpression := '*' | 'null' | 'undefined' | TypeName
* | FunctionType | UnionType | RecordType | ArrayType
*/
private Node parseBasicTypeExpression(JsDocToken token) {
if (token == JsDocToken
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>.STAR) {
return newNode(Token.STAR);
} else if (token == JsDocToken.LB) {
skipEOLs();
return parseArrayType(next());
} else if (token == JsDocToken.LC) {
skipEOLs();
return parseRecordType(next());
} else if (token == JsDocToken.LP) {
skipEOLs();
return parseUnionType(next());
} else if (token == JsDocToken.STRING) {
String string = stream.getString();
if ("function".equals(string)) {
skipEOLs();
return parseFunctionType(next());
} else if ("null".equals(string) || "undefined".equals(string)) {
return newStringNode(string);
} else {
return parseTypeName(token);
}
}
return reportGenericTypeSyntaxWarning();
}
/**
* TypeName := NameExpression | NameExpression TypeApplication
* TypeApplication := '.<' TypeExpressionList '>'
* TypeExpressionList := TypeExpression // a white lie
*/
private Node parseTypeName(JsDocToken token) {
if (token != JsDocToken.STRING) {
return reportGenericTypeSyntaxWarning();
}
String typeName = stream.getString();
while (match(JsDocToken.EOL) &&
typeName.charAt(typeName.length() - 1) == '.') {
skipEOLs();
if (match(JsDocToken.STRING)) {
next();
typeName += stream.getString();
}
}
Node typeNameNode = newStringNode(typeName);
if (match(JsDocToken.LT)) {
next();
skipEOLs();
Node memberType = parseTypeExpressionList(next());
if (memberType != null) {
typeNameNode.addChildToFront(memberType);
skipEOLs();
if (!match(JsDocToken.GT)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.gt");
}
next();
}
}
return typeNameNode;
}
/**
* FunctionType := 'function' FunctionSignatureType
* FunctionSignatureType :=
* TypeParameters '(' 'this' ':' TypeName, ParametersType ')' ResultType
*/
private Node parseFunctionType(JsDocToken token) {
// NOTE(nicksantos): We're not implementing generics at the moment, so
// just throw out TypeParameters.
if (token != JsDocToken.LP) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.lp");
}
Node functionType = newNode(Token.FUNCTION);
Node parameters = null;
skipEOLs();
if (!match(JsDocToken.RP)) {
token = next();
boolean hasParams = true;
if (token == JsDocToken.STRING) {
String tokenStr = stream.getString();
boolean isThis = "this".equals(tokenStr);
boolean isNew = "new".equals(tokenStr);
if (isThis || isNew) {
if (match(JsDocToken.COLON)) {
next();
skipEOLs();
Node contextType = wrapNode(
isThis ? Token.THIS : Token.NEW,
parseTypeName(next()));
if (contextType == null) {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
return null;
}
functionType.addChildToFront(contextType);
} else {
return reportTypeSyntaxWarning("msg.jsdoc.missing.colon");
}
if (match(JsDocToken.COMMA)) {
next();
skipEOLs();
token = next();
} else {
hasParams = false;
}
}
}
if (hasParams) {
parameters = parseParametersType(token);
if (parameters == null) {
return null;
}
}
}
if (parameters != null) {
functionType.addChildToBack(parameters);
}
skipEOLs();
if (!match(JsDocToken.RP)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rp");
}
skipEOLs();
Node resultType = parseResultType(next());
if (resultType == null) {
return null;
} else {
functionType.addChildToBack(resultType);
}
return functionType;
}
/**
* ParametersType := RestParameterType | NonRestParametersType
* | NonRestParametersType ',' RestParameterType
* RestParameterType := '...' Identifier
* NonRestParametersType := ParameterType ',' NonRestParametersType
* | ParameterType
* | OptionalParametersType
* OptionalParametersType := OptionalParameterType
* | OptionalParameterType, OptionalParametersType
* OptionalParameterType := ParameterType=
* ParameterType := TypeExpression | Identifier ':' TypeExpression
*/
// NOTE(nicksantos): The official ES4 grammar forces optional and rest
// arguments to come after the required arguments. Our parser does not
// enforce this. Instead we allow them anywhere in the function at parse-time,
// and then warn about them during type resolution.
//
// In theory, it might be mathematically nicer to do the order-checking here.
// But in practice, the order-checking for structural functions is exactly
// the same as the order-checking for @param annotations. And the latter
// has to happen during type resolution. Rather than duplicate the
// order-checking in two places, we just do all of it in type resolution.
private Node parseParametersType(JsDocToken token) {
Node paramsType = newNode(Token.LP);
boolean isVarArgs = false;
Node paramType = null;
if (token != JsDocToken.RP) {
do {
if (paramType != null) {
// skip past the comma
next();
skipEOLs();
token = next();
}
if (token == JsDocToken.ELLIPSIS) {
// In the latest ES4 proposal, there are no type constraints allowed
// on variable arguments. We support the old syntax for backwards
// compatibility, but we should gradually tear it out.
skipEOLs();
if (match(JsDocToken.RP)) {
paramType = newNode(Token.ELLIPSIS);
} else {
skipEOLs();
if (!match(JsDocToken.LB)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.lb");
}
next();
skipEOLs();
paramType = wrapNode(Token.ELLIPSIS, parseTypeExpression(next()));
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> skipEOLs();
if (!match(JsDocToken.RB)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rb");
}
skipEOLs();
next();
}
isVarArgs = true;
} else {
paramType = parseTypeExpression(token);
if (match(JsDocToken.EQUALS)) {
skipEOLs();
next();
paramType = wrapNode(Token.EQUALS, paramType);
}
}
if (paramType == null) {
return null;
}
paramsType.addChildToBack(paramType);
if (isVarArgs) {
break;
}
} while (match(JsDocToken.COMMA));
}
if (isVarArgs && match(JsDocToken.COMMA)) {
return reportTypeSyntaxWarning("msg.jsdoc.function.varargs");
}
// The right paren will be checked by parseFunctionType
return paramsType;
}
/**
* ResultType := <empty> | ':' void | ':' TypeExpression
*/
private Node parseResultType(JsDocToken token) {
skipEOLs();
if (!match(JsDocToken.COLON)) {
return newNode(Token.EMPTY);
}
token = next();
skipEOLs();
if (match(JsDocToken.STRING) && "void".equals(stream.getString())) {
next();
return newNode(Token.VOID);
} else {
return parseTypeExpression(next());
}
}
/**
* UnionType := '(' TypeUnionList ')'
* TypeUnionList := TypeExpression | TypeExpression '|' TypeUnionList
*
* We've removed the empty union type.
*/
private Node parseUnionType(JsDocToken token) {
return parseUnionTypeWithAlternate(token, null);
}
/**
* Create a new union type, with an alternate that has already been
* parsed. The alternate may be null.
*/
private Node parseUnionTypeWithAlternate(JsDocToken token, Node alternate) {
Node union = newNode(Token.PIPE);
if (alternate != null) {
union.addChildToBack(alternate);
}
Node expr = null;
do {
if (expr != null) {
skipEOLs();
token = next();
Preconditions.checkState(
token == JsDocToken.PIPE || token == JsDocToken.COMMA);
boolean isPipe = token == JsDocToken.PIPE;
if (isPipe && match(JsDocToken.PIPE)) {
// We support double pipes for backwards compatiblity.
next();
}
skipEOLs();
token = next();
}
expr = parseTypeExpression(token);
if (expr == null) {
return null;
}
union.addChildToBack(expr);
// We support commas for backwards compatiblity.
} while (match(JsDocToken.PIPE, JsDocToken.COMMA));
if (alternate == null) {
skipEOLs();
if (!match(JsDocToken.RP)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rp");
}
next();
}
return union;
}
/**
* ArrayType := '['
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> ElementTypeList ']'
* ElementTypeList := <empty> | TypeExpression | '...' TypeExpression
* | TypeExpression ',' ElementTypeList
*/
private Node parseArrayType(JsDocToken token) {
Node array = newNode(Token.LB);
Node arg = null;
boolean hasVarArgs = false;
do {
if (arg != null) {
next();
skipEOLs();
token = next();
}
if (token == JsDocToken.ELLIPSIS) {
arg = wrapNode(Token.ELLIPSIS, parseTypeExpression(next()));
hasVarArgs = true;
} else {
arg = parseTypeExpression(token);
}
if (arg == null) {
return null;
}
array.addChildToBack(arg);
if (hasVarArgs) {
break;
}
skipEOLs();
} while (match(JsDocToken.COMMA));
if (!match(JsDocToken.RB)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rb");
}
next();
return array;
}
/**
* RecordType := '{' FieldTypeList '}'
*/
private Node parseRecordType(JsDocToken token) {
Node recordType = newNode(Token.LC);
Node fieldTypeList = parseFieldTypeList(token);
if (fieldTypeList == null) {
return reportGenericTypeSyntaxWarning();
}
skipEOLs();
if (!match(JsDocToken.RC)) {
return reportTypeSyntaxWarning("msg.jsdoc.missing.rc");
}
next();
recordType.addChildToBack(fieldTypeList);
return recordType;
}
/**
* FieldTypeList := FieldType | FieldType ',' FieldTypeList
*/
private Node parseFieldTypeList(JsDocToken token) {
Node fieldTypeList = newNode(Token.LB);
do {
Node fieldType = parseFieldType(token);
if (fieldType == null) {
return null;
}
fieldTypeList.addChildToBack(fieldType);
skipEOLs();
if (!match(JsDocToken.COMMA)) {
break;
}
// Move to the comma token.
next();
// Move to the token passed the comma.
skipEOLs();
token = next();
} while (true);
return fieldTypeList;
}
/**
* FieldType := FieldName | FieldName ':' TypeExpression
*/
private Node parseFieldType(JsDocToken token) {
Node fieldName = parseFieldName(token);
if (fieldName == null) {
return null;
}
skipEOLs();
if (!match(JsDocToken.COLON)) {
return fieldName;
}
// Move to the colon.
next();
// Move to the token after the colon and parse
// the type expression.
skipEOLs();
Node typeExpression = parseTypeExpression(next());
if (typeExpression == null) {
return null;
}
Node fieldType = newNode(Token.COLON);
fieldType.addChildToBack(fieldName);
fieldType.addChildToBack(typeExpression);
return fieldType;
}
/**
* FieldName := NameExpression | StringLiteral | NumberLiteral |
* ReservedIdentifier
*/
private Node parseFieldName(JsDocToken token)
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> {
switch (token) {
case STRING:
String string = stream.getString();
return newStringNode(string);
default:
return null;
}
}
private Node wrapNode(int type, Node n) {
return n == null ? null :
new Node(type, n, stream.getLineno(),
stream.getCharno()).clonePropsFrom(templateNode);
}
private Node newNode(int type) {
return new Node(type, stream.getLineno(),
stream.getCharno()).clonePropsFrom(templateNode);
}
private Node newStringNode(String s) {
return Node.newString(s, stream.getLineno(),
stream.getCharno()).clonePropsFrom(templateNode);
}
// This is similar to IRFactory.createTemplateNode to share common props
// e.g., source-name, between all nodes.
private Node createTemplateNode() {
// The Node type choice is arbitrary.
Node templateNode = new Node(Token.SCRIPT);
templateNode.putProp(Node.SOURCENAME_PROP, sourceName);
return templateNode;
}
private Node reportTypeSyntaxWarning(String warning) {
parser.addTypeWarning(warning, stream.getLineno(), stream.getCharno());
return null;
}
private Node reportGenericTypeSyntaxWarning() {
return reportTypeSyntaxWarning("msg.jsdoc.type.syntax");
}
/**
* Eats tokens until {@link JsDocToken#EOL} included, and switches back the
* state to {@link State#SEARCHING_ANNOTATION}.
*/
private JsDocToken eatTokensUntilEOL() {
return eatTokensUntilEOL(next());
}
/**
* Eats tokens until {@link JsDocToken#EOL} included, and switches back the
* state to {@link State#SEARCHING_ANNOTATION}.
*/
private JsDocToken eatTokensUntilEOL(JsDocToken token) {
do {
if (token == JsDocToken.EOL || token == JsDocToken.EOC ||
token == JsDocToken.EOF) {
state = State.SEARCHING_ANNOTATION;
return token;
}
token = next();
} while (true);
}
/**
* Specific value indicating that the {@link #unreadToken} contains no token.
*/
private static final JsDocToken NO_UNREAD_TOKEN = null;
/**
* One token buffer.
*/
private JsDocToken unreadToken = NO_UNREAD_TOKEN;
/** Restores the lookahead token to the token stream */
private void restoreLookAhead(JsDocToken token) {
unreadToken = token;
}
/**
* Tests whether the next symbol of the token stream matches the specific
* token.
*/
private boolean match(JsDocToken token) {
unreadToken = next();
return unreadToken == token;
}
/**
* Tests that the next symbol of the token stream matches one of the specified
* tokens.
*/
private boolean match(JsDocToken token1, JsDocToken token2) {
unreadToken = next();
return unreadToken == token1 || unreadToken == token2;
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
/**
* The {@code StaticSlot} interface must be implemented by variables that can
* appear as members of a {@code StaticScope}.
*
* @param <T> The type of information stored about the slot
*/
public interface StaticSlot<T> {
/**
* Gets the name of the slot.
*/
String getName();
/**
* Returns the type information, if any, for this slot.
* @return The type or {@code null} if no type is declared for it.
*/
T getType();
/**
* Returns whether the type has been inferred (as opposed to declared).
*/
boolean isTypeInferred();
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Predicate;
import com.google.javascript.jscomp.ControlFlowGraph.Branch;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.jscomp.graph.GraphNode;
import com.google.javascript.jscomp.graph.GraphReachability;
import com.google.javascript.jscomp.graph.GraphReachability.EdgeTuple;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.TernaryValue;
/**
* Use {@link ControlFlowGraph} and {@link GraphReachability} to inform user
* about unreachable code.
*
*/
class CheckUnreachableCode implements ScopedCallback {
static final DiagnosticType UNREACHABLE_CODE = DiagnosticType.error(
"JSC_UNREACHABLE_CODE", "unreachable code");
private final AbstractCompiler compiler;
private final CheckLevel level;
CheckUnreachableCode(AbstractCompiler compiler, CheckLevel level) {
this.compiler = compiler;
this.level = level;
}
@Override
public void enterScope(NodeTraversal t) {
initScope(t.getControlFlowGraph());
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
GraphNode<Node, Branch> gNode = t.getControlFlowGraph().getNode(n);
if (gNode != null && gNode.getAnnotation() != GraphReachability.REACHABLE) {
// Only report error when there are some line number informations.
// There are synthetic nodes with no line number informations, nodes
// introduce by other passes (although not likely since this pass should
// be executed early) or some rhino bug.
if (n.getLineno() != -1 &&
// Allow spurious semi-colons and spurious breaks.
n.getType() != Token.EMPTY && n.getType() != Token.BREAK) {
compiler.report(t.makeError(n, level, UNREACHABLE_CODE));
// From now on, we are going to assume the user fixed the error and not
// give more warning related to code section reachable from this node.
new GraphReachability<Node, ControlFlowGraph.Branch>(
t.getControlFlowGraph()).recompute(n);
// Saves time by not traversing children.
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> again.
//
// This won't work for any other call flow, or any sort of incremental
// compilation scheme. The API needs to be fixed so callers aren't
// doing weird things like this, and then we should get rid of the
// multiple-scan strategy.
provides.addAll(finder.provides);
requires.addAll(finder.requires);
} else {
// Otherwise, look at the source code.
if (!generatedDependencyInfoFromSource) {
// Note: it's ok to use getName() instead of
// getPathRelativeToClosureBase() here because we're not using
// this to generate deps files. (We're only using it for
// symbol dependencies.)
DependencyInfo info = (new JsFileParser(errorManager)).parseFile(
getName(), getName(), getCode());
provides.addAll(info.getProvides());
requires.addAll(info.getRequires());
generatedDependencyInfoFromSource = true;
}
}
}
private static class DepsFinder {
private final List<String> provides = Lists.newArrayList();
private final List<String> requires = Lists.newArrayList();
private final CodingConvention codingConvention =
new ClosureCodingConvention();
void visitTree(Node n) {
visitSubtree(n, null);
}
void visitSubtree(Node n, Node parent) {
if (n.getType() == Token.CALL) {
String require =
codingConvention.extractClassNameIfRequire(n, parent);
if (require != null) {
requires.add(require);
}
String provide =
codingConvention.extractClassNameIfProvide(n, parent);
if (provide != null) {
provides.add(provide);
}
return;
} else if (parent != null &&
parent.getType() != Token.EXPR_RESULT &&
parent.getType() != Token.SCRIPT) {
return;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
visitSubtree(child, n);
}
}
}
/**
* Gets the source line for the indicated line number.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Does not include the newline at the end
* of the file. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public String getLine(int lineNumber) {
return getSourceFile().getLine(lineNumber);
}
/**
* Get a region around the indicated line number. The exact definition of a
* region is implementation specific, but it must contain the line indicated
* by the line number. A region must not start or end by a carriage return.
*
* @param lineNumber the line number, 1 being the first line of the file.
* @return The line indicated. Returns {@code null} if it does not exist,
* or if there was an IO exception.
*/
public Region getRegion(int lineNumber) {
return getSourceFile().getRegion(lineNumber);
}
public String getCode() throws IOException {
return getSourceFile().getCode();
}
/** Returns the module to which the input
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> message
*/
public static JSError make(String sourceName, int lineno, int charno,
CheckLevel level, DiagnosticType type, String... arguments) {
return new JSError(
sourceName, null, lineno, charno, type, level, arguments);
}
/**
* Creates a JSError from a file and Node position.
*
* @param sourceName The source file name
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(String sourceName, Node n,
DiagnosticType type, String... arguments) {
return new JSError(sourceName, n, type, arguments);
}
/**
* Creates a JSError from a file and Node position.
*
* @param sourceName The source file name
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(String sourceName, Node n, CheckLevel level,
DiagnosticType type, String... arguments) {
return new JSError(sourceName, n, n.getLineno(), n.getCharno(), type, level,
arguments);
}
//
// JSError constructors
//
/**
* Creates a JSError at a CheckLevel for a source file location.
* Private to avoid any entanglement with code outside of the compiler.
*/
private JSError(
String sourceName, @Nullable Node node, int lineno, int charno,
DiagnosticType type, CheckLevel level, String... arguments) {
this.type = type;
this.node = node;
this.description = type.format.format(arguments);
this.lineNumber = lineno;
this.charno = charno;
this.sourceName = sourceName;
this.level = level == null ? type.level : level;
}
/**
* Creates a JSError for a source file location. Private to avoid
* any entanglement with code outside of the compiler.
*/
private JSError(String sourceName, @Nullable Node node,
DiagnosticType type, String... arguments) {
this(sourceName,
node,
(node != null) ? node.getLineno() : -1,
(node != null) ? node.getCharno() : -1,
type, null, arguments);
}
public DiagnosticType getType() {
return type;
}
/**
* Format a message at the given level.
*
* @return the formatted message or {@code null}
*/
public String format(CheckLevel level, MessageFormatter formatter) {
switch (level) {
case ERROR:
return formatter.formatError(this);
case WARNING:
return formatter.formatWarning(this);
default:
return null;
}
}
@Override
public String toString() {
// TODO(user): remove custom toString.
return type.key + ". " + description + " at " +
(sourceName != null && sourceName.length() > 0 ?
sourceName : "(unknown
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Map;
/**
* Filters warnings based on in-code {@code @suppress} annotations.
* @author nicksantos@google.com (Nick Santos)
*/
class SuppressDocWarningsGuard extends WarningsGuard {
private static final long serialVersionUID = 1L;
/** Warnings guards for each suppressable warnings group, indexed by name. */
private final Map<String, DiagnosticGroupWarningsGuard> suppressors =
Maps.newHashMap();
/**
* The suppressable groups, indexed by name.
*/
SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressableGroups) {
for (Map.Entry<String, DiagnosticGroup> entry :
suppressableGroups.entrySet()) {
suppressors.put(
entry.getKey(),
new DiagnosticGroupWarningsGuard(
entry.getValue(),
CheckLevel.OFF));
}
}
@Override
public CheckLevel level(JSError error) {
Node node = error.node;
if (node != null) {
for (Node current = node;
current != null;
current = current.getParent()) {
int type = current.getType();
JSDocInfo info = null;
// We only care about function annotations at the FUNCTION and SCRIPT
// level. Otherwise, the @suppress annotation has an implicit
// dependency on the exact structure of our AST, and that seems like
// a bad idea.
if (type == Token.FUNCTION) {
info = NodeUtil.getFunctionJSDocInfo(current);
} else if (type == Token.SCRIPT) {
info = current.getJSDocInfo();
}
if (info != null) {
for (String suppressor : info.getSuppressions()) {
WarningsGuard guard = suppressors.get(suppressor);
// Some @suppress tags are for other tools, and
// may not have a warnings guard.
if (guard != null) {
CheckLevel newLevel = guard.level(error);
if (newLevel != null) {
return newLevel;
}
}
}
}
}
}
return null;
}
@Override
public int getPriority() {
// Happens after path-based filtering, but before other
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>Expression(NewExpression exprNode);
abstract T processNumberLiteral(NumberLiteral literalNode);
abstract T processObjectLiteral(ObjectLiteral literalNode);
abstract T processObjectProperty(ObjectProperty propertyNode);
abstract T processParenthesizedExpression(ParenthesizedExpression exprNode);
abstract T processPropertyGet(PropertyGet getNode);
abstract T processRegExpLiteral(RegExpLiteral literalNode);
abstract T processReturnStatement(ReturnStatement statementNode);
abstract T processScope(Scope scopeNode);
abstract T processStringLiteral(StringLiteral literalNode);
abstract T processSwitchCase(SwitchCase caseNode);
abstract T processSwitchStatement(SwitchStatement statementNode);
abstract T processThrowStatement(ThrowStatement statementNode);
abstract T processTryStatement(TryStatement statementNode);
abstract T processUnaryExpression(UnaryExpression exprNode);
abstract T processVariableDeclaration(VariableDeclaration declarationNode);
abstract T processVariableInitializer(VariableInitializer initializerNode);
abstract T processWhileLoop(WhileLoop loopNode);
abstract T processWithStatement(WithStatement statementNode);
abstract T processIllegalToken(AstNode node);
public T process(AstNode node) {
switch (node.getType()) {
case Token.ADD:
case Token.AND:
case Token.BITAND:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.IN:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.OR:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return processInfixExpression((InfixExpression) node);
case Token.ARRAYLIT:
return processArrayLiteral((ArrayLiteral) node);
case Token.ASSIGN:
case Token.ASSIGN_ADD:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_DIV:
case Token.ASSIGN_LSH:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_RSH:
case Token.ASSIGN_SUB:
case Token.ASSIGN_URSH:
return processAssignment((Assignment) node);
case Token.BITNOT:
case Token.DEC:
case Token.DELPROP:
case Token.INC:
case Token.NEG:
case Token.NOT:
case Token.POS:
case Token.TYPEOF:
case Token.VOID:
return processUnaryExpression((UnaryExpression) node);
case Token.BLOCK:
if (node instanceof Block) {
return processBlock((Block) node);
} else if (node instanceof Scope) {
return processScope((Scope) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.BREAK:
return processBreakStatement((BreakStatement
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>) node);
case Token.CALL:
return processFunctionCall((FunctionCall) node);
case Token.CASE:
case Token.DEFAULT:
return processSwitchCase((SwitchCase) node);
case Token.CATCH:
case Token.FINALLY:
return processCatchClause((CatchClause) node);
case Token.COLON:
return processObjectProperty((ObjectProperty) node);
case Token.CONTINUE:
return processContinueStatement((ContinueStatement) node);
case Token.DO:
return processDoLoop((DoLoop) node);
case Token.EMPTY:
return processEmptyExpression((EmptyExpression) node);
case Token.EXPR_RESULT:
case Token.EXPR_VOID:
if (node instanceof ExpressionStatement) {
return processExpressionStatement((ExpressionStatement) node);
} else if (node instanceof LabeledStatement) {
return processLabeledStatement((LabeledStatement) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.DEBUGGER:
case Token.FALSE:
case Token.NULL:
case Token.THIS:
case Token.TRUE:
return processKeywordLiteral((KeywordLiteral) node);
case Token.FOR:
if (node instanceof ForInLoop) {
return processForInLoop((ForInLoop) node);
} else if (node instanceof ForLoop) {
return processForLoop((ForLoop) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.FUNCTION:
return processFunctionNode((FunctionNode) node);
case Token.GETELEM:
return processElementGet((ElementGet) node);
case Token.GETPROP:
return processPropertyGet((PropertyGet) node);
case Token.HOOK:
return processConditionalExpression((ConditionalExpression) node);
case Token.IF:
return processIfStatement((IfStatement) node);
case Token.LABEL:
return processLabel((Label) node);
case Token.LP:
return processParenthesizedExpression((ParenthesizedExpression) node);
case Token.NAME:
return processName((Name) node);
case Token.NEW:
return processNewExpression((NewExpression) node);
case Token.NUMBER:
return processNumberLiteral((NumberLiteral) node);
case Token.OBJECTLIT:
return processObjectLiteral((ObjectLiteral) node);
case Token.REGEXP:
return processRegExpLiteral((RegExpLiteral) node);
case Token.RETURN:
return processReturnStatement((ReturnStatement) node);
case Token.SCRIPT:
return processAstRoot((AstRoot) node);
case Token.STRING:
return processStringLiteral((StringLiteral) node);
case Token.SWITCH:
return processSwitchStatement((SwitchStatement) node);
case Token.THROW:
return processThrowStatement((ThrowStatement) node);
case Token.TRY:
return processTryStatement((TryStatement) node);
case Token.CONST:
case Token.VAR:
if (node instanceof VariableDeclaration) {
return processVariableDeclaration((Variable
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>Declaration) node);
} else if (node instanceof VariableInitializer) {
return processVariableInitializer((VariableInitializer) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.WHILE:
return processWhileLoop((WhileLoop) node);
case Token.WITH:
return processWithStatement((WithStatement) node);
}
return processIllegalToken(node);
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.TernaryValue;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/**
* NodeUtil contains utilities that get properties from the Node object.
*
*/
public final class NodeUtil {
static final long MAX_POSITIVE_INTEGER_NUMBER = (long)Math.pow(2, 53);
final static String JSC_PROPERTY_NAME_FN = "JSCompiler_renameProperty";
// TODO(user): Eliminate this class and make all of the static methods
// instance methods of com.google.javascript.rhino.Node.
/** the set of builtin constructors that don't have side effects. */
private static final Set<String> CONSTRUCTORS_WITHOUT_SIDE_EFFECTS =
new HashSet<String>(Arrays.asList(
"Array",
"Date",
"Error",
"Object",
"RegExp",
"XMLHttpRequest"));
// Utility class; do not instantiate.
private NodeUtil() {}
/**
* Gets the boolean value of a node that represents a expression. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function.
* Note: unlike getBooleanValue this function does not return UNKNOWN
* for expressions with side-effects.
*/
static TernaryValue getImpureBooleanValue(Node n) {
switch (n.getType()) {
case Token.ASSIGN:
case Token.COMMA:
// For ASSIGN and COMMA the value is the value of the RHS.
return getImpureBooleanValue(n.getLastChild());
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
case Token.NOT:
TernaryValue value = getImpureBooleanValue(n.getLastChild());
return value.not();
case Token.AND: {
TernaryValue lhs = getImpureBooleanValue(n.getFirstChild());
TernaryValue rhs = getImpureBooleanValue(n.getLastChild());
return lhs.and(rhs);
}
case Token.OR: {
TernaryValue lhs = getImpureBooleanValue(n.getFirstChild());
TernaryValue rhs = getImpureBooleanValue(n.getLastChild());
return lhs.or(rhs);
}
case Token.HOOK: {
TernaryValue trueValue = getImpureBooleanValue(
n.getFirstChild().getNext());
TernaryValue falseValue = getImpureBooleanValue(n.getLastChild());
if (trueValue.equals(falseValue)) {
return trueValue;
} else {
return TernaryValue.UNKNOWN;
}
}
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// ignoring side-effects
return TernaryValue.TRUE;
default:
return getPureBooleanValue(n);
}
}
/**
* Gets the boolean value of a node that represents a literal. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function
* except it return UNKNOWN for known values with side-effects, use
* getExpressionBooleanValue if you don't care about side-effects.
*/
static TernaryValue getPureBooleanValue(Node n) {
switch (n.getType()) {
case Token.STRING:
return TernaryValue.forBoolean(n.getString().length() > 0);
case Token.NUMBER:
return TernaryValue.forBoolean(n.getDouble() != 0);
case Token.NOT:
return getPureBooleanValue(n.getLastChild()).not();
case Token.NULL:
case Token.FALSE:
case Token.VOID:
return TernaryValue.FALSE;
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "NaN".equals(name)) {
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return TernaryValue.FALSE;
} else if ("Infinity".equals(name)) {
return TernaryValue.TRUE;
}
break;
case Token.TRUE:
case Token.REGEXP:
return TernaryValue.TRUE;
case Token.ARRAYLIT:
case Token.OBJECTLIT:
if (!mayHaveSideEffects(n)) {
return TernaryValue.TRUE;
}
}
return TernaryValue.UNKNOWN;
}
/**
* Gets the value of a node as a String, or null if it cannot be converted.
* When it returns a non-null String, this method effectively emulates the
* <code>String()</code> JavaScript cast function.
*/
static String getStringValue(Node n) {
// TODO(user): regex literals as well.
switch (n.getType()) {
case Token.STRING:
return n.getString();
case Token.NAME:
String name = n.getString();
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
if ("undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name)) {
return name;
}
break;
case Token.NUMBER:
return getStringValue(n.getDouble());
case Token.FALSE:
case Token.TRUE:
case Token.NULL:
return Node.tokenToName(n.getType());
case Token.VOID:
return "undefined";
case Token.NOT:
TernaryValue child = getPureBooleanValue(n.getFirstChild());
if (child != TernaryValue.UNKNOWN) {
return child.toBoolean(true) ? "false" : "true"; // reversed.
}
break;
case Token.ARRAYLIT:
return arrayToString(n);
case Token.OBJECTLIT:
return "[object Object]";
}
return null;
}
static String getStringValue(double value) {
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString(value);
}
}
/**
* When converting arrays to string using Array.prototype.toString or
* Array.prototype.join, the rules for conversion to String are different
* than converting each element individually. Specifically, "null" and
* "undefined" are converted to an empty string.
* @param n A node that is a member of an Array.
* @return The string representation.
*/
static String getArrayElementStringValue(Node n) {
return (NodeUtil.isNullOrUndefined(n) || n.getType() == Token.EMPTY)
? "" : getStringValue(n);
}
static String arrayToString(Node literal) {
Node first = literal.getFirstChild();
StringBuilder result = new StringBuilder();
int nextSlot = 0;
int nextSkipSlot = 0;
for (Node n = first; n != null; n = n.getNext()) {
String childValue = getArrayElementStringValue(n);
if (childValue == null) {
return null;
}
if (n != first) {
result.append(',');
}
result.append(childValue);
nextSlot++;
}
return result.toString();
}
/**
* Gets the value of a node as a Number, or null if it cannot be converted.
* When it returns a non-null Double, this method effectively emulates the
* <code>Number()</code> JavaScript cast function.
*/
static Double getNumberValue(Node n) {
switch (n.getType()) {
case Token.TRUE:
return 1.0;
case Token.FALSE:
case Token.NULL:
return 0.0;
case Token.NUMBER:
return n.getDouble();
case Token.VOID:
if (mayHaveSideEffects(n.getFirstChild())) {
return null;
} else {
return Double.NaN;
}
case Token.NAME:
// Check for known constants
String name = n.getString();
if (name.equals("undefined")) {
return Double.NaN;
}
if (name.equals("NaN"))
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> {
return Double.NaN;
}
if (name.equals("Infinity")) {
return Double.POSITIVE_INFINITY;
}
return null;
case Token.NEG:
if (n.getChildCount() == 1 && n.getFirstChild().getType() == Token.NAME
&& n.getFirstChild().getString().equals("Infinity")) {
return Double.NEGATIVE_INFINITY;
}
return null;
case Token.NOT:
TernaryValue child = getPureBooleanValue(n.getFirstChild());
if (child != TernaryValue.UNKNOWN) {
return child.toBoolean(true) ? 0.0 : 1.0; // reversed.
}
break;
case Token.STRING:
return getStringNumberValue(n.getString());
case Token.ARRAYLIT:
case Token.OBJECTLIT:
String value = getStringValue(n);
return value != null ? getStringNumberValue(value) : null;
}
return null;
}
static Double getStringNumberValue(String rawJsString) {
if (rawJsString.contains("\u000b")) {
// vertical tab is not always whitespace
return null;
}
String s = trimJsWhiteSpace(rawJsString);
// return ScriptRuntime.toNumber(s);
if (s.length() == 0) {
return 0.0;
}
if (s.length() > 2
&& s.charAt(0) == '0'
&& (s.charAt(1) == 'x' || s.charAt(1) == 'X')) {
// Attempt to convert hex numbers.
try {
return Double.valueOf(Integer.parseInt(s.substring(2), 16));
} catch (NumberFormatException e) {
return Double.NaN;
}
}
if (s.length() > 3
&& (s.charAt(0) == '-' || s.charAt(0) == '+')
&& s.charAt(1) == '0'
&& (s.charAt(2) == 'x' || s.charAt(2) == 'X')) {
// hex numbers with explicit signs vary between browsers.
return null;
}
// FireFox and IE treat the "Infinity" differently. FireFox is case
// insensitive, but IE treats "infinity" as NaN. So leave it alone.
if (s.equals("infinity")
|| s.equals("-infinity")
|| s.equals("+infinity")) {
return null;
}
try {
return Double.parseDouble(s);
} catch (NumberFormatException e) {
return Double.NaN;
}
}
static String trimJsWhiteSpace(String s) {
int start = 0;
int end = s.length();
while (end > 0
&& isStrWhiteSpaceChar(s.charAt(end - 1)) == TernaryValue.TRUE) {
end--;
}
while (start < end
&& isStrWhiteSpaceChar(s.charAt(start)) == TernaryValue.TRUE) {
start++;
}
return s.substring(start, end);
}
/**
* Copied from Rhino's ScriptRuntime
*/
static
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> TernaryValue isStrWhiteSpaceChar(int c) {
switch (c) {
case '\u000B': // <VT>
return TernaryValue.UNKNOWN; // IE says "no", EcmaScript says "yes"
case ' ': // <SP>
case '\n': // <LF>
case '\r': // <CR>
case '\t': // <TAB>
case '\u00A0': // <NBSP>
case '\u000C': // <FF>
case '\u2028': // <LS>
case '\u2029': // <PS>
case '\uFEFF': // <BOM>
return TernaryValue.TRUE;
default:
return (Character.getType(c) == Character.SPACE_SEPARATOR)
? TernaryValue.TRUE : TernaryValue.FALSE;
}
}
/**
* Gets the function's name. This method recognizes five forms:
* <ul>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
* In two last cases with named function expressions, the second name is
* returned (the variable of qualified name).
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getFunctionName(Node n) {
Node parent = n.getParent();
String name = n.getFirstChild().getString();
switch (parent.getType()) {
case Token.NAME:
// var name = function() ...
// var name2 = function name1() ...
return parent.getString();
case Token.ASSIGN:
// qualified.name = function() ...
// qualified.name2 = function name1() ...
return parent.getFirstChild().getQualifiedName();
default:
// function name() ...
return name != null && name.length() != 0 ? name : null;
}
}
/**
* Gets the function's name. This method recognizes the forms:
* <ul>
* <li>{@code {'name': function() ...}}</li>
* <li>{@code {name: function() ...}}</li>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
static String getNearestFunctionName(
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>Node n) {
String name = getFunctionName(n);
if (name != null) {
return name;
}
// Check for the form { 'x' : function() { } }
Node parent = n.getParent();
switch (parent.getType()) {
case Token.SET:
case Token.GET:
case Token.STRING:
// Return the name of the literal's key.
return parent.getString();
case Token.NUMBER:
return getStringValue(parent);
}
return null;
}
/**
* Returns true if this is an immutable value.
*/
static boolean isImmutableValue(Node n) {
switch (n.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.NULL:
case Token.TRUE:
case Token.FALSE:
return true;
case Token.NOT:
return isImmutableValue(n.getFirstChild());
case Token.VOID:
case Token.NEG:
return isImmutableValue(n.getFirstChild());
case Token.NAME:
String name = n.getString();
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return "undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name);
}
return false;
}
/**
* Returns true if this is a literal value. We define a literal value
* as any node that evaluates to the same thing regardless of when or
* where it is evaluated. So /xyz/ and [3, 5] are literals, but
* the name a is not.
*
* Function literals do not meet this definition, because they
* lexically capture variables. For example, if you have
* <code>
* function() { return a; }
* </code>
* If it is evaluated in a different scope, then it
* captures a different variable. Even if the function did not read
* any captured vairables directly, it would still fail this definition,
* because it affects the lifecycle of variables in the enclosing scope.
*
* However, a function literal with respect to a particular scope is
* a literal.
*
* @param includeFunctions If true, all function expressions will be
* treated as literals.
*/
static boolean isLiteralValue(Node n, boolean includeFunctions) {
switch (n.getType()) {
case Token.ARRAYLIT:
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (child.getType() != Token.EMPTY
&& !isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.REGEXP:
// Return true only if all children are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.OBJECTLIT:
// Return true only if all values are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> if (!isLiteralValue(child.getFirstChild(), includeFunctions)) {
return false;
}
}
return true;
case Token.FUNCTION:
return includeFunctions && !NodeUtil.isFunctionDeclaration(n);
default:
return isImmutableValue(n);
}
}
/**
* Determines whether the given value may be assigned to a define.
*
* @param val The value being assigned.
* @param defines The list of names of existing defines.
*/
static boolean isValidDefineValue(Node val, Set<String> defines) {
switch (val.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
return true;
// Binary operators are only valid if both children are valid.
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return isValidDefineValue(val.getFirstChild(), defines)
&& isValidDefineValue(val.getLastChild(), defines);
// Uniary operators are valid if the child is valid.
case Token.NOT:
case Token.NEG:
case Token.POS:
return isValidDefineValue(val.getFirstChild(), defines);
// Names are valid if and only if they are defines themselves.
case Token.NAME:
case Token.GETPROP:
if (val.isQualifiedName()) {
return defines.contains(val.getQualifiedName());
}
}
return false;
}
/**
* Returns whether this a BLOCK node with no children.
*
* @param block The node.
*/
static boolean isEmptyBlock(Node block) {
if (block.getType() != Token.BLOCK) {
return false;
}
for (Node n = block.getFirstChild(); n != null; n = n.getNext()) {
if (n.getType() != Token.EMPTY) {
return false;
}
}
return true;
}
static boolean isSimpleOperator(Node n) {
return isSimpleOperatorType(n.getType());
}
/**
* A "simple" operator is one whose children are expressions,
* has no direct side-effects (unlike '+='), and has no
* conditional aspects (unlike '||').
*/
static boolean isSimpleOperatorType(int type) {
switch (type) {
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GETELEM:
case Token.GETPROP:
case Token.GT:
case Token.INSTANCEOF:
case Token.LE:
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.NOT:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.TYPEOF:
case Token.VOID:
case Token.POS:
case Token.NEG:
case Token.URSH:
return true;
default:
return false;
}
}
/**
* Creates an EXPR_RESULT.
*
* @param child The expression itself.
* @return Newly created EXPR node with the child as subexpression.
*/
public static Node newExpr(Node child) {
Node expr = new Node(Token.EXPR_RESULT, child)
.copyInformationFrom(child);
return expr;
}
/**
* Returns true if the node may create new mutable state, or change existing
* state.
*
* @see <a href="http://www.xkcd.org/326/">XKCD Cartoon</a>
*/
static boolean mayEffectMutableState(Node n) {
return mayEffectMutableState(n, null);
}
static boolean mayEffectMutableState(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, true, compiler);
}
/**
* Returns true if the node which may have side effects when executed.
*/
static boolean mayHaveSideEffects(Node n) {
return mayHaveSideEffects(n, null);
}
static boolean mayHaveSideEffects(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, false, compiler);
}
/**
* Returns true if some node in n's subtree changes application state.
* If {@code checkForNewObjects} is true, we assume that newly created
* mutable objects (like object literals) change state. Otherwise, we assume
* that they have no side effects.
*/
private static boolean checkForStateChangeHelper(
Node n, boolean checkForNewObjects, AbstractCompiler compiler) {
// Rather than id which ops may have side effects, id the ones
// that we know to be safe
switch (n.getType()) {
// other side-effect free statements and expressions
case Token.AND:
case Token.BLOCK:
case Token.EXPR_RESULT:
case Token.HOOK:
case Token.IF:
case Token.IN:
case Token.LP:
case Token.NUMBER:
case Token.OR:
case Token.THIS:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
case Token.STRING:
case Token.SWITCH:
case Token.TRY:
case Token.EMPTY:
break;
// Throws are by definition side effects
case Token.THROW:
return true;
case Token.OBJECTLIT:
if (checkForNewObjects) {
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(
c.getFirstChild(), checkForNewObjects, compiler)) {
return true;
}
}
return false
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>;
case Token.ARRAYLIT:
case Token.REGEXP:
if (checkForNewObjects) {
return true;
}
break;
case Token.VAR: // empty var statement (no declaration)
case Token.NAME: // variable by itself
if (n.getFirstChild() != null) {
return true;
}
break;
case Token.FUNCTION:
// Function expressions don't have side-effects, but function
// declarations change the namespace. Either way, we don't need to
// check the children, since they aren't executed at declaration time.
return checkForNewObjects || !isFunctionExpression(n);
case Token.NEW:
if (checkForNewObjects) {
return true;
}
if (!constructorCallHasSideEffects(n)) {
// loop below will see if the constructor parameters have
// side-effects
break;
}
return true;
case Token.CALL:
// calls to functions that have no side effects have the no
// side effect property set.
if (!functionCallHasSideEffects(n, compiler)) {
// loop below will see if the function parameters have
// side-effects
break;
}
return true;
default:
if (isSimpleOperatorType(n.getType())) {
break;
}
if (isAssignmentOp(n)) {
Node assignTarget = n.getFirstChild();
if (isName(assignTarget)) {
return true;
}
// Assignments will have side effects if
// a) The RHS has side effects, or
// b) The LHS has side effects, or
// c) A name on the LHS will exist beyond the life of this statement.
if (checkForStateChangeHelper(
n.getFirstChild(), checkForNewObjects, compiler) ||
checkForStateChangeHelper(
n.getLastChild(), checkForNewObjects, compiler)) {
return true;
}
if (isGet(assignTarget)) {
// If the object being assigned to is a local object, don't
// consider this a side-effect as it can't be referenced
// elsewhere. Don't do this recursively as the property might
// be an alias of another object, unlike a literal below.
Node current = assignTarget.getFirstChild();
if (evaluatesToLocalValue(current)) {
return false;
}
// A literal value as defined by "isLiteralValue" is guaranteed
// not to be an alias, or any components which are aliases of
// other objects.
// If the root object is a literal don't consider this a
// side-effect.
while (isGet(current)) {
current = current.getFirstChild();
}
return !isLiteralValue(current, true);
} else {
// TODO(johnlenz): remove this code and make this an exception. This
// is here only for legacy reasons, the AST is not valid but
// preserve existing behavior.
return !isLiteralValue(assignTarget, true);
}
}
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(c, checkForNewObjects, compiler)) {
return
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> true;
}
}
return false;
}
/**
* Do calls to this constructor have side effects?
*
* @param callNode - construtor call node
*/
static boolean constructorCallHasSideEffects(Node callNode) {
return constructorCallHasSideEffects(callNode, null);
}
static boolean constructorCallHasSideEffects(
Node callNode, AbstractCompiler compiler) {
if (callNode.getType() != Token.NEW) {
throw new IllegalStateException(
"Expected NEW node, got " + Token.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
if (nameNode.getType() == Token.NAME &&
CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(nameNode.getString())) {
return false;
}
return true;
}
// A list of built-in object creation or primitive type cast functions that
// can also be called as constructors but lack side-effects.
// TODO(johnlenz): consider adding an extern annotation for this.
private static final Set<String> BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS =
ImmutableSet.of(
"Object", "Array", "String", "Number", "Boolean", "RegExp", "Error");
private static final Set<String> OBJECT_METHODS_WITHOUT_SIDEEFFECTS =
ImmutableSet.of("toString", "valueOf");
private static final Set<String> REGEXP_METHODS =
ImmutableSet.of("test", "exec");
private static final Set<String> STRING_REGEXP_METHODS =
ImmutableSet.of("match", "replace", "search", "split");
/**
* Returns true if calls to this function have side effects.
*
* @param callNode - function call node
*/
static boolean functionCallHasSideEffects(Node callNode) {
return functionCallHasSideEffects(callNode, null);
}
/**
* Returns true if calls to this function have side effects.
*
* @param callNode The call node to inspected.
* @param compiler A compiler object to provide program state changing
* context information. Can be null.
*/
static boolean functionCallHasSideEffects(
Node callNode, @Nullable AbstractCompiler compiler) {
if (callNode.getType() != Token.CALL) {
throw new IllegalStateException(
"Expected CALL node, got " + Token.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
// Built-in functions with no side effects.
if (nameNode.getType() == Token.NAME) {
String name = nameNode.getString();
if (BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS.contains(name)) {
return false;
}
} else if (nameNode.getType() == Token.GETPROP) {
if (callNode.hasOneChild()
&& OBJECT_METHODS_WITHOUT_SIDEEFFECTS.contains(
nameNode.getLastChild().getString())) {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> return false;
}
if (callNode.isOnlyModifiesThisCall()
&& evaluatesToLocalValue(nameNode.getFirstChild())) {
return false;
}
// Functions in the "Math" namespace have no side effects.
if (nameNode.getFirstChild().getType() == Token.NAME) {
String namespaceName = nameNode.getFirstChild().getString();
if (namespaceName.equals("Math")) {
return false;
}
}
if (compiler != null && !compiler.hasRegExpGlobalReferences()) {
if (nameNode.getFirstChild().getType() == Token.REGEXP
&& REGEXP_METHODS.contains(nameNode.getLastChild().getString())) {
return false;
} else if (nameNode.getFirstChild().getType() == Token.STRING
&& STRING_REGEXP_METHODS.contains(
nameNode.getLastChild().getString())) {
Node param = nameNode.getNext();
if (param != null &&
(param.getType() == Token.STRING
|| param.getType() == Token.REGEXP))
return false;
}
}
}
return true;
}
/**
* @return Whether the call has a local result.
*/
static boolean callHasLocalResult(Node n) {
Preconditions.checkState(n.getType() == Token.CALL);
return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0;
}
/**
* @return Whether the new has a local result.
*/
static boolean newHasLocalResult(Node n) {
Preconditions.checkState(n.getType() == Token.NEW);
return n.isOnlyModifiesThisCall();
}
/**
* Returns true if the current node's type implies side effects.
*
* This is a non-recursive version of the may have side effects
* check; used to check wherever the current node's type is one of
* the reason's why a subtree has side effects.
*/
static boolean nodeTypeMayHaveSideEffects(Node n) {
return nodeTypeMayHaveSideEffects(n, null);
}
static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) {
if (isAssignmentOp(n)) {
return true;
}
switch(n.getType()) {
case Token.DELPROP:
case Token.DEC:
case Token.INC:
case Token.THROW:
return true;
case Token.CALL:
return NodeUtil.functionCallHasSideEffects(n, compiler);
case Token.NEW:
return NodeUtil.constructorCallHasSideEffects(n, compiler);
case Token.NAME:
// A variable definition.
return n.hasChildren();
default:
return false;
}
}
/**
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n) {
Set<String> emptySet = Collections.emptySet();
return canBeSideEffected(n, emptySet);
}
/**
* @param knownConstants A set of names known to be constant value at
* node 'n' (such as locals that are last written before
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> n can execute).
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
case Token.FUNCTION:
// Function expression are not changed by side-effects,
// and function declarations are not part of expressions.
Preconditions.checkState(isFunctionExpression(n));
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
* 3 logical-or ||
* 4 logical-and &&
* 5 bitwise-or |
* 6 bitwise-xor ^
* 7 bitwise-and &
* 8 equality == !=
* 9 relational < <= > >=
* 10 bitwise shift << >> >>>
* 11 addition/subtraction + -
* 12 multiply/divide * / %
* 13 negation/increment ! ~ - ++ --
* 14 call, member () [] .
*/
static int precedence(int type) {
switch (type) {
case Token.COMMA: return 0;
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN: return 1;
case Token.HOOK: return 2; // ?: operator
case Token.OR: return 3;
case Token.AND: return 4;
case Token.BITOR: return 5;
case Token.BITXOR: return 6;
case Token.BITAND: return 7;
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE: return 8;
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
case Token.INSTANCEOF:
case Token.IN: return 9;
case Token.L
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>SH:
case Token.RSH:
case Token.URSH: return 10;
case Token.SUB:
case Token.ADD: return 11;
case Token.MUL:
case Token.MOD:
case Token.DIV: return 12;
case Token.INC:
case Token.DEC:
case Token.NEW:
case Token.DELPROP:
case Token.TYPEOF:
case Token.VOID:
case Token.NOT:
case Token.BITNOT:
case Token.POS:
case Token.NEG: return 13;
case Token.CALL:
case Token.GETELEM:
case Token.GETPROP:
// Data values
case Token.ARRAYLIT:
case Token.EMPTY: // TODO(johnlenz): remove this.
case Token.FALSE:
case Token.FUNCTION:
case Token.NAME:
case Token.NULL:
case Token.NUMBER:
case Token.OBJECTLIT:
case Token.REGEXP:
case Token.STRING:
case Token.THIS:
case Token.TRUE:
return 15;
default: throw new Error("Unknown precedence for " +
Node.tokenToName(type) +
" (type " + type + ")");
}
}
/**
* Apply the supplied predicate against the potential
* all possible result of the expression.
*/
static boolean valueCheck(Node n, Predicate<Node> p) {
switch (n.getType()) {
case Token.ASSIGN:
case Token.COMMA:
return valueCheck(n.getLastChild(), p);
case Token.AND:
case Token.OR:
return valueCheck(n.getFirstChild(), p)
&& valueCheck(n.getLastChild(), p);
case Token.HOOK:
return valueCheck(n.getFirstChild().getNext(), p)
&& valueCheck(n.getLastChild(), p);
default:
return p.apply(n);
}
}
static class NumbericResultPredicate implements Predicate<Node> {
public boolean apply(Node n) {
return isNumericResultHelper(n);
}
}
static final NumbericResultPredicate NUMBERIC_RESULT_PREDICATE =
new NumbericResultPredicate();
/**
* Returns true if the result of node evaluation is always a number
*/
static boolean isNumericResult(Node n) {
return valueCheck(n, NUMBERIC_RESULT_PREDICATE);
}
static boolean isNumericResultHelper(Node n) {
switch (n.getType()) {
case Token.ADD:
return !mayBeString(n.getFirstChild())
&& !mayBeString(n.getLastChild());
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.SUB:
case Token.MUL:
case Token.MOD:
case Token.DIV:
case Token.INC:
case Token.DEC:
case Token.POS:
case Token.NEG:
case Token.NUMBER:
return true;
case Token.NAME:
String name
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> = n.getString();
if (name.equals("NaN")) {
return true;
}
if (name.equals("Infinity")) {
return true;
}
return false;
default:
return false;
}
}
static class BooleanResultPredicate implements Predicate<Node> {
public boolean apply(Node n) {
return isBooleanResultHelper(n);
}
}
static final BooleanResultPredicate BOOLEAN_RESULT_PREDICATE =
new BooleanResultPredicate();
/**
* @return Whether the result of node evaluation is always a boolean
*/
static boolean isBooleanResult(Node n) {
return valueCheck(n, BOOLEAN_RESULT_PREDICATE);
}
static boolean isBooleanResultHelper(Node n) {
switch (n.getType()) {
// Primitives
case Token.TRUE:
case Token.FALSE:
// Comparisons
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
// Queryies
case Token.IN:
case Token.INSTANCEOF:
// Inversion
case Token.NOT:
// delete operator returns a boolean.
case Token.DELPROP:
return true;
default:
return false;
}
}
static boolean isUndefined(Node n) {
switch (n.getType()) {
case Token.VOID:
return true;
case Token.NAME:
return n.getString().equals("undefined");
}
return false;
}
static boolean isNull(Node n) {
return n.getType() == Token.NULL;
}
static boolean isNullOrUndefined(Node n) {
return isNull(n) || isUndefined(n);
}
static class MayBeStringResultPredicate implements Predicate<Node> {
public boolean apply(Node n) {
return mayBeStringHelper(n);
}
}
static final MayBeStringResultPredicate MAY_BE_STRING_PREDICATE =
new MayBeStringResultPredicate();
/**
* @returns Whether the results is possibly a string.
*/
static boolean mayBeString(Node n) {
return mayBeString(n, true);
}
static boolean mayBeString(Node n, boolean recurse) {
if (recurse) {
return valueCheck(n, MAY_BE_STRING_PREDICATE);
} else {
return mayBeStringHelper(n);
}
}
static boolean mayBeStringHelper(Node n) {
return !isNumericResult(n) && !isBooleanResult(n)
&& !isUndefined(n) && !isNull(n);
}
/**
* Returns true if the operator is associative.
* e.g. (a * b) * c = a * (b * c)
* Note: "+" is not associative because it is also the concatenation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
*/
static boolean isAssociative(int type) {
switch (type) {
case Token.MUL:
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return true;
default:
return false;
}
}
/**
* Returns true if the operator is commutative.
* e.g. (a * b) * c = c * (b * a)
* Note 1: "+" is not commutative because it is also the concatenation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
* Note 2: only operations on literals and pure functions are commutative.
*/
static boolean isCommutative(int type) {
switch (type) {
case Token.MUL:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return true;
default:
return false;
}
}
static boolean isAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
return true;
}
return false;
}
static int getOpFromAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN_BITOR:
return Token.BITOR;
case Token.ASSIGN_BITXOR:
return Token.BITXOR;
case Token.ASSIGN_BITAND:
return Token.BITAND;
case Token.ASSIGN_LSH:
return Token.LSH;
case Token.ASSIGN_RSH:
return Token.RSH;
case Token.ASSIGN_URSH:
return Token.URSH;
case Token.ASSIGN_ADD:
return Token.ADD;
case Token.ASSIGN_SUB:
return Token.SUB;
case Token.ASSIGN_MUL:
return Token.MUL;
case Token.ASSIGN_DIV:
return Token.DIV;
case Token.ASSIGN_MOD:
return Token.MOD;
}
throw new IllegalArgumentException("Not an assiment op");
}
static boolean isExpressionNode(Node n) {
return n.getType() == Token.EXPR_RESULT;
}
/**
* Determines if the given node contains a function statement or function
* expression.
*/
static boolean containsFunction(Node n) {
return containsType(n, Token.FUNCTION);
}
/**
* Returns true if the shallow scope contains references to 'this' keyword
*/
static boolean referencesThis(Node n) {
Node start = (isFunction(n)) ? n.getLastChild() : n;
return containsType(start, Token.THIS, new MatchNotFunction());
}
/**
* Is this a GETPROP or GETELEM node?
*/
static boolean isGet(Node n) {
return n
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>.getType() == Token.GETPROP
|| n.getType() == Token.GETELEM;
}
/**
* Is this a GETPROP node?
*/
static boolean isGetProp(Node n) {
return n.getType() == Token.GETPROP;
}
/**
* Is this a NAME node?
*/
static boolean isName(Node n) {
return n.getType() == Token.NAME;
}
/**
* Is this a NEW node?
*/
static boolean isNew(Node n) {
return n.getType() == Token.NEW;
}
/**
* Is this a VAR node?
*/
static boolean isVar(Node n) {
return n.getType() == Token.VAR;
}
/**
* Is this node the name of a variable being declared?
*
* @param n The node
* @return True if {@code n} is NAME and {@code parent} is VAR
*/
static boolean isVarDeclaration(Node n) {
// There is no need to verify that parent != null because a NAME node
// always has a parent in a valid parse tree.
return n.getType() == Token.NAME && n.getParent().getType() == Token.VAR;
}
/**
* For an assignment or variable declaration get the assigned value.
* @return The value node representing the new value.
*/
static Node getAssignedValue(Node n) {
Preconditions.checkState(isName(n));
Node parent = n.getParent();
if (isVar(parent)) {
return n.getFirstChild();
} else if (isAssign(parent) && parent.getFirstChild() == n) {
return n.getNext();
} else {
return null;
}
}
/**
* Is this a STRING node?
*/
static boolean isString(Node n) {
return n.getType() == Token.STRING;
}
/**
* Is this node an assignment expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is ASSIGN
*/
static boolean isExprAssign(Node n) {
return n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.ASSIGN;
}
/**
* Is this an ASSIGN node?
*/
static boolean isAssign(Node n) {
return n.getType() == Token.ASSIGN;
}
/**
* Is this node a call expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is CALL
*/
static boolean isExprCall(Node n) {
return n.getType() == Token.EXPR_RESULT
&& n.getFirstChild().getType() == Token.CALL;
}
/**
* @return Whether the node represents a FOR-IN loop.
*/
static boolean isForIn(Node n) {
return n.getType() == Token.FOR
&& n.getChildCount() == 3;
}
/**
* Determines whether the given node is a FOR, DO, or WHILE
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> node.
*/
static boolean isLoopStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* @param n The node to inspect.
* @return If the node, is a FOR, WHILE, or DO, it returns the node for
* the code BLOCK, null otherwise.
*/
static Node getLoopCodeBlock(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.WHILE:
return n.getLastChild();
case Token.DO:
return n.getFirstChild();
default:
return null;
}
}
/**
* @return Whether the specified node has a loop parent that
* is within the current scope.
*/
static boolean isWithinLoop(Node n) {
for (Node parent : n.getAncestors()) {
if (NodeUtil.isLoopStructure(parent)) {
return true;
}
if (NodeUtil.isFunction(parent)) {
break;
}
}
return false;
}
/**
* Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node.
*/
static boolean isControlStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.WITH:
case Token.IF:
case Token.LABEL:
case Token.TRY:
case Token.CATCH:
case Token.SWITCH:
case Token.CASE:
case Token.DEFAULT:
return true;
default:
return false;
}
}
/**
* Determines whether the given node is code node for FOR, DO,
* WHILE, WITH, or IF node.
*/
static boolean isControlStructureCodeBlock(Node parent, Node n) {
switch (parent.getType()) {
case Token.FOR:
case Token.WHILE:
case Token.LABEL:
case Token.WITH:
return parent.getLastChild() == n;
case Token.DO:
return parent.getFirstChild() == n;
case Token.IF:
return parent.getFirstChild() != n;
case Token.TRY:
return parent.getFirstChild() == n || parent.getLastChild() == n;
case Token.CATCH:
return parent.getLastChild() == n;
case Token.SWITCH:
case Token.CASE:
return parent.getFirstChild() != n;
case Token.DEFAULT:
return true;
default:
Preconditions.checkState(isControlStructure(parent));
return false;
}
}
/**
* Gets the condition of an ON_TRUE / ON_FALSE CFG edge.
* @param n a node with an outgoing conditional CFG edge
* @return the condition node or null if the condition is not obviously a node
*/
static Node getConditionExpression(Node n) {
switch (n.getType()) {
case Token.IF:
case Token.WHILE:
return n.getFirstChild();
case Token.DO:
return n.getLastChild();
case Token
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>.FOR:
switch (n.getChildCount()) {
case 3:
return null;
case 4:
return n.getFirstChild().getNext();
}
throw new IllegalArgumentException("malformed 'for' statement " + n);
case Token.CASE:
return null;
}
throw new IllegalArgumentException(n + " does not have a condition.");
}
/**
* @return Whether the node is of a type that contain other statements.
*/
static boolean isStatementBlock(Node n) {
return n.getType() == Token.SCRIPT || n.getType() == Token.BLOCK;
}
/**
* @return Whether the node is used as a statement.
*/
static boolean isStatement(Node n) {
return isStatementParent(n.getParent());
}
static boolean isStatementParent(Node parent) {
// It is not possible to determine definitely if a node is a statement
// or not if it is not part of the AST. A FUNCTION node can be
// either part of an expression or a statement.
Preconditions.checkState(parent != null);
switch (parent.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.LABEL:
return true;
default:
return false;
}
}
/** Whether the node is part of a switch statement. */
static boolean isSwitchCase(Node n) {
return n.getType() == Token.CASE || n.getType() == Token.DEFAULT;
}
/**
* @return Whether the name is a reference to a variable, function or
* function parameter (not a label or a empty function expression name).
*/
static boolean isReferenceName(Node n) {
return isName(n) && !n.getString().isEmpty();
}
/** @return Whether the node is a label name. */
static boolean isLabelName(Node n) {
return (n != null && n.getType() == Token.LABEL_NAME);
}
/** Whether the child node is the FINALLY block of a try. */
static boolean isTryFinallyNode(Node parent, Node child) {
return parent.getType() == Token.TRY && parent.getChildCount() == 3
&& child == parent.getLastChild();
}
/** Whether the node is a CATCH container BLOCK. */
static boolean isTryCatchNodeContainer(Node n) {
Node parent = n.getParent();
return parent.getType() == Token.TRY
&& parent.getFirstChild().getNext() == n;
}
/** Safely remove children while maintaining a valid node structure. */
static void removeChild(Node parent, Node node) {
if (isTryFinallyNode(parent, node)) {
if (NodeUtil.hasCatchHandler(getCatchBlock(parent))) {
// A finally can only be removed if there is a catch.
parent.removeChild(node);
} else {
// Otherwise only its children can be removed.
node.detachChildren();
}
} else if (node.getType() == Token.CATCH) {
// The CATCH can can only be removed if there is a finally clause.
Node tryNode = node.getParent().getParent();
Preconditions.checkState(NodeUtil.hasFinally(tryNode
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>));
node.detachFromParent();
} else if (isTryCatchNodeContainer(node)) {
// The container node itself can't be removed, but the contained CATCH
// can if there is a 'finally' clause
Node tryNode = node.getParent();
Preconditions.checkState(NodeUtil.hasFinally(tryNode));
node.detachChildren();
} else if (node.getType() == Token.BLOCK) {
// Simply empty the block. This maintains source location and
// "synthetic"-ness.
node.detachChildren();
} else if (isStatementBlock(parent)
|| isSwitchCase(node)) {
// A statement in a block can simply be removed.
parent.removeChild(node);
} else if (parent.getType() == Token.VAR) {
if (parent.hasMoreThanOneChild()) {
parent.removeChild(node);
} else {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// This would leave an empty VAR, remove the VAR itself.
removeChild(parent.getParent(), parent);
}
} else if (parent.getType() == Token.LABEL
&& node == parent.getLastChild()) {
// Remove the node from the parent, so it can be reused.
parent.removeChild(node);
// A LABEL without children can not be referred to, remove it.
removeChild(parent.getParent(), parent);
} else if (parent.getType() == Token.FOR
&& parent.getChildCount() == 4) {
// Only Token.FOR can have an Token.EMPTY other control structure
// need something for the condition. Others need to be replaced
// or the structure removed.
parent.replaceChild(node, new Node(Token.EMPTY));
} else {
throw new IllegalStateException("Invalid attempt to remove node: " +
node.toString() + " of "+ parent.toString());
}
}
/**
* Add a finally block if one does not exist.
*/
static void maybeAddFinally(Node tryNode) {
Preconditions.checkState(tryNode.getType() == Token.TRY);
if (!NodeUtil.hasFinally(tryNode)) {
tryNode.addChildrenToBack(new Node(Token.BLOCK)
.copyInformationFrom(tryNode));
}
}
/**
* Merge a block with its parent block.
* @return Whether the block was removed.
*/
static boolean tryMergeBlock(Node block) {
Preconditions.checkState(block.getType() == Token.BLOCK);
Node parent = block.getParent();
// Try to remove the block if its parent is a block/script or if its
// parent is label and it has exactly one child.
if (isStatementBlock(parent)) {
Node previous = block;
while (block.hasChildren()) {
Node child = block.removeFirstChild();
parent.addChildAfter(child, previous);
previous = child;
}
parent.removeChild(block);
return true;
} else {
return false;
}
}
/**
* Is this a CALL node?
*/
static boolean isCall(Node n) {
return n.getType() == Token.CALL;
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
/**
* @param node A node
* @return Whether the call is a NEW or CALL node.
*/
static boolean isCallOrNew(Node node) {
return NodeUtil.isCall(node) || NodeUtil.isNew(node);
}
/**
* Is this a FUNCTION node?
*/
static boolean isFunction(Node n) {
return n.getType() == Token.FUNCTION;
}
/**
* Return a BLOCK node for the given FUNCTION node.
*/
static Node getFunctionBody(Node fn) {
Preconditions.checkArgument(isFunction(fn));
return fn.getLastChild();
}
/**
* Is this a THIS node?
*/
static boolean isThis(Node node) {
return node.getType() == Token.THIS;
}
/**
* Is this an ARRAYLIT node
*/
static boolean isArrayLiteral(Node node) {
return node.getType() == Token.ARRAYLIT;
}
/**
* Is this node or any of its children a CALL?
*/
static boolean containsCall(Node n) {
return containsType(n, Token.CALL);
}
/**
* Is this node a function declaration? A function declaration is a function
* that has a name that is added to the current scope (i.e. a function that
* is not part of a expression; see {@link #isFunctionExpression}).
*/
static boolean isFunctionDeclaration(Node n) {
return n.getType() == Token.FUNCTION && isStatement(n);
}
/**
* Is this node a hoisted function declaration? A function declaration in the
* scope root is hoisted to the top of the scope.
* See {@link #isFunctionDeclaration}).
*/
static boolean isHoistedFunctionDeclaration(Node n) {
return isFunctionDeclaration(n)
&& (n.getParent().getType() == Token.SCRIPT
|| n.getParent().getParent().getType() == Token.FUNCTION);
}
/**
* Is a FUNCTION node an function expression? An function expression is one
* that has either no name or a name that is not added to the current scope.
*
* <p>Some examples of function expressions:
* <pre>
* (function () {})
* (function f() {})()
* [ function f() {} ]
* var f = function f() {};
* for (function f() {};;) {}
* </pre>
*
* <p>Some examples of functions that are <em>not</em> expressions:
* <pre>
* function f() {}
* if (x); else function f() {}
* for (;;) { function f() {} }
* </pre>
*
* @param n A node
* @return Whether n is an function used within an expression.
*/
static boolean isFunctionExpression(Node n) {
return n.getType() == Token.FUNCTION && !isStatement(n);
}
/**
* Determines if a node is a function expression that has an empty body.
*
* @param node a node
* @return whether the given node is a function expression that is empty
*/
static boolean isEmptyFunctionExpression(Node node) {
return isFunctionExpression(node) && isEmptyBlock
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>(node.getLastChild());
}
/**
* Determines if a function takes a variable number of arguments by
* looking for references to the "arguments" var_args object.
*/
static boolean isVarArgsFunction(Node function) {
Preconditions.checkArgument(isFunction(function));
return isNameReferenced(
function.getLastChild(),
"arguments",
new MatchNotFunction());
}
/**
* @return Whether node is a call to methodName.
* a.f(...)
* a['f'](...)
*/
static boolean isObjectCallMethod(Node callNode, String methodName) {
if (callNode.getType() == Token.CALL) {
Node functionIndentifyingExpression = callNode.getFirstChild();
if (isGet(functionIndentifyingExpression)) {
Node last = functionIndentifyingExpression.getLastChild();
if (last != null && last.getType() == Token.STRING) {
String propName = last.getString();
return (propName.equals(methodName));
}
}
}
return false;
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.call(...)
* x['call'](...)
*/
static boolean isFunctionObjectCall(Node callNode) {
return isObjectCallMethod(callNode, "call");
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.apply(...)
* x['apply'](...)
*/
static boolean isFunctionObjectApply(Node callNode) {
return isObjectCallMethod(callNode, "apply");
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.apply(...)
* x['apply'](...)
* or
* x.call(...)
* x['call'](...)
*/
static boolean isFunctionObjectCallOrApply(Node callNode) {
return isFunctionObjectCall(callNode) || isFunctionObjectApply(callNode);
}
/**
* @return Whether the callNode represents an expression in the form of:
* x.call(...)
* x['call'](...)
* where x is a NAME node.
*/
static boolean isSimpleFunctionObjectCall(Node callNode) {
if (isFunctionObjectCall(callNode)) {
if (callNode.getFirstChild().getFirstChild().getType() == Token.NAME) {
return true;
}
}
return false;
}
/**
* Determines whether this node is strictly on the left hand side of an assign
* or var initialization. Notably, this does not include all L-values, only
* statements where the node is used only as an L-value.
*
* @param n The node
* @param parent Parent of the node
* @return True if n is the left hand of an assign
*/
static boolean isVarOrSimpleAssignLhs(Node n, Node parent) {
return (parent.getType() == Token.ASSIGN && parent.getFirstChild() == n) ||
parent.getType() == Token.VAR;
}
/**
* Determines whether this node is used as an L-value. Notice that sometimes
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> * names are used as both L-values and R-values.
*
* We treat "var x;" as a pseudo-L-value, which kind of makes sense if you
* treat it as "assignment to 'undefined' at the top of the scope". But if
* we're honest with ourselves, it doesn't make sense, and we only do this
* because it makes sense to treat this as synactically similar to
* "var x = 0;".
*
* @param node The node
* @return True if n is an L-value.
*/
static boolean isLValue(Node node) {
int nType = node.getType();
Preconditions.checkArgument(nType == Token.NAME || nType == Token.GETPROP ||
nType == Token.GETELEM);
Node parent = node.getParent();
return (NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == node)
|| (NodeUtil.isForIn(parent) && parent.getFirstChild() == node)
|| NodeUtil.isVar(parent)
|| (parent.getType() == Token.FUNCTION &&
parent.getFirstChild() == node)
|| parent.getType() == Token.DEC
|| parent.getType() == Token.INC
|| parent.getType() == Token.LP
|| parent.getType() == Token.CATCH;
}
/**
* Determines whether a node represents an object literal key
* (e.g. key1 in {key1: value1, key2: value2}).
*
* @param node A node
* @param parent The node's parent
*/
static boolean isObjectLitKey(Node node, Node parent) {
switch (node.getType()) {
case Token.STRING:
return parent.getType() == Token.OBJECTLIT;
case Token.GET:
case Token.SET:
return true;
}
return false;
}
/**
* Get the name of an object literal key.
*
* @param key A node
*/
static String getObjectLitKeyName(Node key) {
switch (key.getType()) {
case Token.STRING:
case Token.GET:
case Token.SET:
return key.getString();
}
throw new IllegalStateException("Unexpected node type: " + key);
}
/**
* @param key A OBJECTLIT key node.
* @return The type expected when using the key.
*/
static JSType getObjectLitKeyTypeFromValueType(Node key, JSType valueType) {
if (valueType != null) {
switch (key.getType()) {
case Token.GET:
// GET must always return a function type.
if (valueType.isFunctionType()) {
FunctionType fntype = ((FunctionType) valueType);
valueType = fntype.getReturnType();
} else {
return null;
}
break;
case Token.SET:
if (valueType.isFunctionType()) {
// SET must always return a function type.
FunctionType fntype = ((FunctionType) valueType);
Node param = fntype.getParametersNode().getFirstChild();
// SET function must always have one parameter.
valueType = param.getJSType();
} else {
return
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> null;
}
break;
}
}
return valueType;
}
/**
* Determines whether a node represents an object literal get or set key
* (e.g. key1 in {get key1() {}, set key2(a){}).
*
* @param node A node
*/
static boolean isGetOrSetKey(Node node) {
switch (node.getType()) {
case Token.GET:
case Token.SET:
return true;
}
return false;
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation.
*
* @param operator the operator's token value to convert
* @return the string representation or {@code null} if the token value is
* not an operator
*/
static String opToStr(int operator) {
switch (operator) {
case Token.BITOR: return "|";
case Token.OR: return "||";
case Token.BITXOR: return "^";
case Token.AND: return "&&";
case Token.BITAND: return "&";
case Token.SHEQ: return "===";
case Token.EQ: return "==";
case Token.NOT: return "!";
case Token.NE: return "!=";
case Token.SHNE: return "!==";
case Token.LSH: return "<<";
case Token.IN: return "in";
case Token.LE: return "<=";
case Token.LT: return "<";
case Token.URSH: return ">>>";
case Token.RSH: return ">>";
case Token.GE: return ">=";
case Token.GT: return ">";
case Token.MUL: return "*";
case Token.DIV: return "/";
case Token.MOD: return "%";
case Token.BITNOT: return "~";
case Token.ADD: return "+";
case Token.SUB: return "-";
case Token.POS: return "+";
case Token.NEG: return "-";
case Token.ASSIGN: return "=";
case Token.ASSIGN_BITOR: return "|=";
case Token.ASSIGN_BITXOR: return "^=";
case Token.ASSIGN_BITAND: return "&=";
case Token.ASSIGN_LSH: return "<<=";
case Token.ASSIGN_RSH: return ">>=";
case Token.ASSIGN_URSH: return ">>>=";
case Token.ASSIGN_ADD: return "+=";
case Token.ASSIGN_SUB: return "-=";
case Token.ASSIGN_MUL: return "*=";
case Token.ASSIGN_DIV: return "/=";
case Token.ASSIGN_MOD: return "%=";
case Token.VOID: return "void";
case Token.TYPEOF: return "typeof";
case Token.INSTANCEOF: return "instanceof";
default: return null;
}
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation or fails.
*
* @param operator the operator's token value to convert
* @return the string representation
* @throws Error if the token value is not an operator
*/
static String opToStrNoFail
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>(int operator) {
String res = opToStr(operator);
if (res == null) {
throw new Error("Unknown op " + operator + ": " +
Token.name(operator));
}
return res;
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node,
int type,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* @return true if n or any of its children are of the specified type
*/
static boolean containsType(Node node, int type) {
return containsType(node, type, Predicates.<Node>alwaysTrue());
}
/**
* Given a node tree, finds all the VAR declarations in that tree that are
* not in an inner scope. Then adds a new VAR node at the top of the current
* scope that redeclares them, if necessary.
*/
static void redeclareVarsInsideBranch(Node branch) {
Collection<Node> vars = getVarsDeclaredInBranch(branch);
if (vars.isEmpty()) {
return;
}
Node parent = getAddingRoot(branch);
for (Node nameNode : vars) {
Node var = new Node(
Token.VAR,
Node.newString(Token.NAME, nameNode.getString())
.copyInformationFrom(nameNode))
.copyInformationFrom(nameNode);
copyNameAnnotations(nameNode, var.getFirstChild());
parent.addChildToFront(var);
}
}
/**
* Copy any annotations that follow a named value.
* @param source
* @param destination
*/
static void copyNameAnnotations(Node source, Node destination) {
if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) {
destination.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
/**
* Gets a Node at the top of the current scope where we can add new var
* declarations as children.
*/
private static Node getAddingRoot(Node n) {
Node addingRoot = null;
Node ancestor = n;
while (null != (ancestor = ancestor.getParent())) {
int type = ancestor.getType();
if (type == Token.SCRIPT) {
addingRoot = ancestor;
break;
} else if (type == Token.FUNCTION) {
addingRoot = ancestor.getLastChild();
break;
}
}
// make sure that the adding root looks ok
Preconditions.checkState(addingRoot.getType() == Token.BLOCK ||
addingRoot.getType() == Token.SCRIPT);
Preconditions.checkState(addingRoot.getFirstChild() == null ||
addingRoot.getFirstChild().getType() != Token.SCRIPT);
return addingRoot;
}
/** Creates function name(params_0, ..., params_n) { body }. */
public static Node newFunctionNode(String name, List<Node> params,
Node body, int lineno, int charno) {
Node parameterParen = new Node(Token.LP, lineno, charno);
for (Node param : params) {
parameterParen.addChildToBack(param);
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> }
Node function = new Node(Token.FUNCTION, lineno, charno);
function.addChildrenToBack(
Node.newString(Token.NAME, name, lineno, charno));
function.addChildToBack(parameterParen);
function.addChildToBack(body);
return function;
}
/**
* Creates a node representing a qualified name.
*
* @param name A qualified name (e.g. "foo" or "foo.bar.baz")
* @param lineno The source line offset.
* @param charno The source character offset from start of the line.
* @return A NAME or GETPROP node
*/
public static Node newQualifiedNameNode(
CodingConvention convention, String name, int lineno, int charno) {
int endPos = name.indexOf('.');
if (endPos == -1) {
return newName(convention, name, lineno, charno);
}
Node node = newName(
convention, name.substring(0, endPos), lineno, charno);
int startPos;
do {
startPos = endPos + 1;
endPos = name.indexOf('.', startPos);
String part = (endPos == -1
? name.substring(startPos)
: name.substring(startPos, endPos));
Node propNode = Node.newString(Token.STRING, part, lineno, charno);
if (convention.isConstantKey(part)) {
propNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
node = new Node(Token.GETPROP, node, propNode, lineno, charno);
} while (endPos != -1);
return node;
}
/**
* Creates a node representing a qualified name, copying over the source
* location information from the basis node and assigning the given original
* name to the node.
*
* @param name A qualified name (e.g. "foo" or "foo.bar.baz")
* @param basisNode The node that represents the name as currently found in
* the AST.
* @param originalName The original name of the item being represented by the
* NAME node. Used for debugging information.
*
* @return A NAME or GETPROP node
*/
static Node newQualifiedNameNode(
CodingConvention convention, String name, Node basisNode,
String originalName) {
Node node = newQualifiedNameNode(convention, name, -1, -1);
setDebugInformation(node, basisNode, originalName);
return node;
}
/**
* Gets the root node of a qualified name. Must be either NAME or THIS.
*/
public static Node getRootOfQualifiedName(Node qName) {
for (Node current = qName; true;
current = current.getFirstChild()) {
int type = current.getType();
if (type == Token.NAME || type == Token.THIS) {
return current;
}
Preconditions.checkState(type == Token.GETPROP);
}
}
/**
* Sets the debug information (source file info and orignal name)
* on the given node.
*
* @param node The node on which to set the debug information.
*
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> @param basisNode The basis node from which to copy the source file info.
* @param originalName The original name of the node.
*/
static void setDebugInformation(Node node, Node basisNode,
String originalName) {
node.copyInformationFromForTree(basisNode);
node.putProp(Node.ORIGINALNAME_PROP, originalName);
}
private static Node newName(
CodingConvention convention, String name, int lineno, int charno) {
Node nameNode = Node.newString(Token.NAME, name, lineno, charno);
if (convention.isConstant(name)) {
nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
return nameNode;
}
/**
* Creates a new node representing an *existing* name, copying over the source
* location information from the basis node.
*
* @param name The name for the new NAME node.
* @param basisNode The node that represents the name as currently found in
* the AST.
*
* @return The node created.
*/
static Node newName(
CodingConvention convention, String name, Node basisNode) {
Node nameNode = Node.newString(Token.NAME, name);
if (convention.isConstantKey(name)) {
nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
nameNode.copyInformationFrom(basisNode);
return nameNode;
}
/**
* Creates a new node representing an *existing* name, copying over the source
* location information from the basis node and assigning the given original
* name to the node.
*
* @param name The name for the new NAME node.
* @param basisNode The node that represents the name as currently found in
* the AST.
* @param originalName The original name of the item being represented by the
* NAME node. Used for debugging information.
*
* @return The node created.
*/
static Node newName(
CodingConvention convention, String name,
Node basisNode, String originalName) {
Node nameNode = newName(convention, name, basisNode);
nameNode.putProp(Node.ORIGINALNAME_PROP, originalName);
return nameNode;
}
/** Test if all characters in the string are in the Basic Latin (aka ASCII)
* character set - that they have UTF-16 values equal to or below 0x7f.
* This check can find which identifiers with Unicode characters need to be
* escaped in order to allow resulting files to be processed by non-Unicode
* aware UNIX tools and editors.
* *
* See http://en.wikipedia.org/wiki/Latin_characters_in_Unicode
* for more on Basic Latin.
*
* @param s The string to be checked for ASCII-goodness.
*
* @return True if all characters in the string are in Basic Latin set.
*/
static boolean isLatin(String s) {
char LARGEST_BASIC_LATIN = 0x7f;
int len = s.length();
for (int index = 0; index < len; index++) {
char c =
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> s.charAt(index);
if (c > LARGEST_BASIC_LATIN) {
return false;
}
}
return true;
}
/**
* Determines whether the given name can appear on the right side of
* the dot operator. Many properties (like reserved words) cannot.
*/
static boolean isValidPropertyName(String name) {
return TokenStream.isJSIdentifier(name) &&
!TokenStream.isKeyword(name) &&
// no Unicode escaped characters - some browsers are less tolerant
// of Unicode characters that might be valid according to the
// language spec.
// Note that by this point, unicode escapes have been converted
// to UTF-16 characters, so we're only searching for character
// values, not escapes.
isLatin(name);
}
private static class VarCollector implements Visitor {
final Map<String, Node> vars = Maps.newLinkedHashMap();
public void visit(Node n) {
if (n.getType() == Token.NAME) {
Node parent = n.getParent();
if (parent != null && parent.getType() == Token.VAR) {
String name = n.getString();
if (!vars.containsKey(name)) {
vars.put(name, n);
}
}
}
}
}
/**
* Retrieves vars declared in the current node tree, excluding descent scopes.
*/
public static Collection<Node> getVarsDeclaredInBranch(Node root) {
VarCollector collector = new VarCollector();
visitPreOrder(
root,
collector,
new MatchNotFunction());
return collector.vars.values();
}
/**
* @return {@code true} if the node an assignment to a prototype property of
* some constructor.
*/
static boolean isPrototypePropertyDeclaration(Node n) {
if (!isExprAssign(n)) {
return false;
}
return isPrototypeProperty(n.getFirstChild().getFirstChild());
}
static boolean isPrototypeProperty(Node n) {
String lhsString = n.getQualifiedName();
if (lhsString == null) {
return false;
}
int prototypeIdx = lhsString.indexOf(".prototype.");
return prototypeIdx != -1;
}
/**
* @return The class name part of a qualified prototype name.
*/
static Node getPrototypeClassName(Node qName) {
Node cur = qName;
while (isGetProp(cur)) {
if (cur.getLastChild().getString().equals("prototype")) {
return cur.getFirstChild();
} else {
cur = cur.getFirstChild();
}
}
return null;
}
/**
* @return The string property name part of a qualified prototype name.
*/
static String getPrototypePropertyName(Node qName) {
String qNameStr = qName.getQualifiedName();
int prototypeIdx = qNameStr.lastIndexOf(".prototype.");
int memberIndex = prototypeIdx + ".prototype".length() + 1;
return qNameStr.substring(memberIndex);
}
/**
* Create a node for an empty result expression:
* "void 0"
*/
static Node newUndefinedNode(Node srcReferenceNode) {
Node node = new
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> Node(Token.VOID, Node.newNumber(0));
if (srcReferenceNode != null) {
node.copyInformationFromForTree(srcReferenceNode);
}
return node;
}
/**
* Create a VAR node containing the given name and initial value expression.
*/
static Node newVarNode(String name, Node value) {
Node nodeName = Node.newString(Token.NAME, name);
if (value != null) {
Preconditions.checkState(value.getNext() == null);
nodeName.addChildToBack(value);
nodeName.copyInformationFrom(value);
}
Node var = new Node(Token.VAR, nodeName)
.copyInformationFrom(nodeName);
return var;
}
/**
* A predicate for matching name nodes with the specified node.
*/
private static class MatchNameNode implements Predicate<Node>{
final String name;
MatchNameNode(String name){
this.name = name;
}
public boolean apply(Node n) {
return n.getType() == Token.NAME
&& n.getString().equals(name);
}
}
/**
* A predicate for matching nodes with the specified type.
*/
static class MatchNodeType implements Predicate<Node>{
final int type;
MatchNodeType(int type){
this.type = type;
}
public boolean apply(Node n) {
return n.getType() == type;
}
}
/**
* A predicate for matching var or function declarations.
*/
static class MatchDeclaration implements Predicate<Node> {
public boolean apply(Node n) {
return isFunctionDeclaration(n) || n.getType() == Token.VAR;
}
}
/**
* A predicate for matching anything except function nodes.
*/
static class MatchNotFunction implements Predicate<Node>{
public boolean apply(Node n) {
return !isFunction(n);
}
}
/**
* A predicate for matching statements without exiting the current scope.
*/
static class MatchShallowStatement implements Predicate<Node>{
public boolean apply(Node n) {
Node parent = n.getParent();
return n.getType() == Token.BLOCK
|| (!isFunction(n) && (parent == null
|| isControlStructure(parent)
|| isStatementBlock(parent)));
}
}
/**
* Finds the number of times a type is referenced within the node tree.
*/
static int getNodeTypeReferenceCount(
Node node, int type, Predicate<Node> traverseChildrenPred) {
return getCount(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node,
String name,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNameNode(name), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node, String name) {
return isNameReferenced(node, name, Predicates.<Node>alwaysTrue());
}
/**
* Finds the number of times a simple name is referenced within the node tree.
*/
static int getNameReferenceCount(Node node
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>, String name) {
return getCount(
node, new MatchNameNode(name), Predicates.<Node>alwaysTrue());
}
/**
* @return Whether the predicate is true for the node or any of its children.
*/
static boolean has(Node node,
Predicate<Node> pred,
Predicate<Node> traverseChildrenPred) {
if (pred.apply(node)) {
return true;
}
if (!traverseChildrenPred.apply(node)) {
return false;
}
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
if (has(c, pred, traverseChildrenPred)) {
return true;
}
}
return false;
}
/**
* @return The number of times the the predicate is true for the node
* or any of its children.
*/
static int getCount(
Node n, Predicate<Node> pred, Predicate<Node> traverseChildrenPred) {
int total = 0;
if (pred.apply(n)) {
total++;
}
if (traverseChildrenPred.apply(n)) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
total += getCount(c, pred, traverseChildrenPred);
}
}
return total;
}
/**
* Interface for use with the visit method.
* @see #visit
*/
static interface Visitor {
void visit(Node node);
}
/**
* A pre-order traversal, calling Vistor.visit for each child matching
* the predicate.
*/
static void visitPreOrder(Node node,
Visitor vistor,
Predicate<Node> traverseChildrenPred) {
vistor.visit(node);
if (traverseChildrenPred.apply(node)) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPreOrder(c, vistor, traverseChildrenPred);
}
}
}
/**
* A post-order traversal, calling Vistor.visit for each child matching
* the predicate.
*/
static void visitPostOrder(Node node,
Visitor vistor,
Predicate<Node> traverseChildrenPred) {
if (traverseChildrenPred.apply(node)) {
for (Node c = node.getFirstChild(); c != null; c = c.getNext()) {
visitPostOrder(c, vistor, traverseChildrenPred);
}
}
vistor.visit(node);
}
/**
* @return Whether a TRY node has a finally block.
*/
static boolean hasFinally(Node n) {
Preconditions.checkArgument(n.getType() == Token.TRY);
return n.getChildCount() == 3;
}
/**
* @return The BLOCK node containing the CATCH node (if any)
* of a TRY.
*/
static Node getCatchBlock(Node n) {
Preconditions.checkArgument(n.getType() == Token.TRY);
return n.getFirstChild().getNext();
}
/**
* @return Whether BLOCK (from a TRY node) contains a CATCH.
* @see NodeUtil#getCatchBlock
*/
static boolean has
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>CatchHandler(Node n) {
Preconditions.checkArgument(n.getType() == Token.BLOCK);
return n.hasChildren() && n.getFirstChild().getType() == Token.CATCH;
}
/**
* @param fnNode The function.
* @return The Node containing the Function parameters.
*/
public static Node getFunctionParameters(Node fnNode) {
// Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.getType() == Token.FUNCTION);
return fnNode.getFirstChild().getNext();
}
/**
* Returns true if a name node represents a constant variable.
*
* <p>Determining whether a variable is constant has three steps:
* <ol>
* <li>In CodingConventionAnnotator, any name that matches the
* {@link CodingConvention#isConstant(String)} is annotated with an
* IS_CONSTANT_NAME property.
* <li>The normalize pass renames any variable with the IS_CONSTANT_NAME
* annotation and that is initialized to a constant value with
* a variable name inlucding $$constant.
* <li>Return true here if the variable includes $$constant in its name.
* </ol>
*
* @param node A NAME or STRING node
* @return True if the variable is constant
*/
static boolean isConstantName(Node node) {
return node.getBooleanProp(Node.IS_CONSTANT_NAME);
}
/** Whether the given name is constant by coding convention. */
static boolean isConstantByConvention(
CodingConvention convention, Node node, Node parent) {
String name = node.getString();
if (parent.getType() == Token.GETPROP &&
node == parent.getLastChild()) {
return convention.isConstantKey(name);
} else if (isObjectLitKey(node, parent)) {
return convention.isConstantKey(name);
} else {
return convention.isConstant(name);
}
}
/**
* @param nameNode A name node
* @return The JSDocInfo for the name node
*/
static JSDocInfo getInfoForNameNode(Node nameNode) {
JSDocInfo info = null;
Node parent = null;
if (nameNode != null) {
info = nameNode.getJSDocInfo();
parent = nameNode.getParent();
}
if (info == null && parent != null &&
((parent.getType() == Token.VAR && parent.hasOneChild()) ||
parent.getType() == Token.FUNCTION)) {
info = parent.getJSDocInfo();
}
return info;
}
/**
* Get the JSDocInfo for a function.
*/
public static JSDocInfo getFunctionJSDocInfo(Node n) {
Preconditions.checkState(n.getType() == Token.FUNCTION);
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null && NodeUtil.isFunctionExpression(n)) {
// Look for the info on other nodes.
Node parent = n.getParent();
if (parent.getType() == Token.ASSIGN) {
// on ASSIGN
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>s
fnInfo = parent.getJSDocInfo();
} else if (parent.getType() == Token.NAME) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
return fnInfo;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
public static String getSourceName(Node n) {
String sourceName = null;
while (sourceName == null && n != null) {
sourceName = (String) n.getProp(Node.SOURCENAME_PROP);
n = n.getParent();
}
return sourceName;
}
/**
* A new CALL node with the "FREE_CALL" set based on call target.
*/
static Node newCallNode(Node callTarget, Node... parameters) {
boolean isFreeCall = isName(callTarget);
Node call = new Node(Token.CALL, callTarget);
call.putBooleanProp(Node.FREE_CALL, isFreeCall);
for (Node parameter : parameters) {
call.addChildToBack(parameter);
}
return call;
}
/**
* @return Whether the node is known to be a value that is not referenced
* elsewhere.
*/
static boolean evaluatesToLocalValue(Node value) {
return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse());
}
/**
* @param locals A predicate to apply to unknown local values.
* @return Whether the node is known to be a value that is not a reference
* outside the expression scope.
*/
static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) {
switch (value.getType()) {
case Token.ASSIGN:
// A result that is aliased by a non-local name, is the effectively the
// same as returning a non-local name, but this doesn't matter if the
// value is immutable.
return NodeUtil.isImmutableValue(value.getLastChild())
|| (locals.apply(value)
&& evaluatesToLocalValue(value.getLastChild(), locals));
case Token.COMMA:
return evaluatesToLocalValue(value.getLastChild(), locals);
case Token.AND:
case Token.OR:
return evaluatesToLocalValue(value.getFirstChild(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.HOOK:
return evaluatesToLocalValue(value.getFirstChild().getNext(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.INC:
case Token.DEC:
if (value.getBooleanProp(Node.INCRDECR_PROP)) {
return evaluatesToLocalValue(value.getFirstChild(), locals);
} else {
return true;
}
case Token.THIS:
return locals.apply(value);
case Token.NAME:
return isImmutableValue(value) || locals.apply(value);
case Token.GETELEM:
case Token.GETPROP:
// There is no information about the locality of object properties.
return locals.apply(value);
case Token.CALL:
return callHasLocalResult(value)
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> || isToStringMethodCall(value)
|| locals.apply(value);
case Token.NEW:
return newHasLocalResult(value)
|| locals.apply(value);
case Token.FUNCTION:
case Token.REGEXP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// Literals objects with non-literal children are allowed.
return true;
case Token.DELPROP:
case Token.IN:
// TODO(johnlenz): should IN operator be included in #isSimpleOperator?
return true;
default:
// Other op force a local value:
// x = '' + g (x is now an local string)
// x -= g (x is now an local number)
if (isAssignmentOp(value)
|| isSimpleOperator(value)
|| isImmutableValue(value)) {
return true;
}
throw new IllegalStateException(
"Unexpected expression node" + value +
"\n parent:" + value.getParent());
}
}
/**
* Given the first sibling, this returns the nth
* sibling or null if no such sibling exists.
* This is like "getChildAtIndex" but returns null for non-existent indexes.
*/
private static Node getNthSibling(Node first, int index) {
Node sibling = first;
while (index != 0 && sibling != null) {
sibling = sibling.getNext();
index--;
}
return sibling;
}
/**
* Given the function, this returns the nth
* argument or null if no such parameter exists.
*/
static Node getArgumentForFunction(Node function, int index) {
Preconditions.checkState(isFunction(function));
return getNthSibling(
function.getFirstChild().getNext().getFirstChild(), index);
}
/**
* Given the new or call, this returns the nth
* argument of the call or null if no such argument exists.
*/
static Node getArgumentForCallOrNew(Node call, int index) {
Preconditions.checkState(isCallOrNew(call));
return getNthSibling(
call.getFirstChild().getNext(), index);
}
private static boolean isToStringMethodCall(Node call) {
Node getNode = call.getFirstChild();
if (isGet(getNode)) {
Node propNode = getNode.getLastChild();
return isString(propNode) && "toString".equals(propNode.getString());
}
return false;
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> null) {
inExterns = true;
NodeTraversal.traverse(compiler, externs, this);
}
if (root != null) {
inExterns = false;
NodeTraversal.traverse(compiler, root, this);
}
}
public void visit(NodeTraversal t, Node n, Node parent) {
JSDocInfo docInfo;
switch (n.getType()) {
// Infer JSDocInfo on types of all type declarations on variables.
case Token.NAME:
if (parent == null) {
return;
}
// Only allow JSDoc on VARs, function declarations, and assigns.
if (parent.getType() != Token.VAR &&
!NodeUtil.isFunctionDeclaration(parent) &&
!(parent.getType() == Token.ASSIGN &&
n == parent.getFirstChild())) {
return;
}
// There are four places the doc info could live.
// 1) A FUNCTION node.
// /** ... */ function f() { ... }
// 2) An ASSIGN parent.
// /** ... */ x = function () { ... }
// 3) A NAME parent.
// var x, /** ... */ y = function() { ... }
// 4) A VAR gramps.
// /** ... */ var x = function() { ... }
docInfo = n.getJSDocInfo();
if (docInfo == null &&
!(parent.getType() == Token.VAR &&
!parent.hasOneChild())) {
docInfo = parent.getJSDocInfo();
}
// Try to find the type of the NAME.
JSType varType = n.getJSType();
if (varType == null && parent.getType() == Token.FUNCTION) {
varType = parent.getJSType();
}
// If we have no type to attach JSDocInfo to, then there's nothing
// we can do.
if (varType == null || docInfo == null) {
return;
}
// Dereference the type. If the result is not an object, or already
// has docs attached, then do nothing.
ObjectType objType = dereferenceToObject(varType);
if (objType == null || objType.getJSDocInfo() != null) {
return;
}
attachJSDocInfoToNominalTypeOrShape(objType, docInfo, n.getString());
break;
case Token.GETPROP:
// Infer JSDocInfo on properties.
// There are two ways to write doc comments on a property.
//
// 1)
// /** @deprecated */
// obj.prop = ...
//
// 2)
// /** @deprecated */
// obj.prop;
if (NodeUtil.isExpressionNode(parent) ||
(parent.getType() == Token.ASSIGN &&
parent.getFirstChild() == n)) {
docInfo = n.getJSDocInfo();
if (docInfo == null) {
docInfo = parent.getJSDocInfo();
}
if (docInfo != null) {
ObjectType lhsType =
dereferenceToObject(n.getFirstChild().getJSType());
if (lhsType !=
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> boolean isFrozen = false;
/**
* Creates a record type.
*
* @param registry The type registry under which this type lives.
* @param properties A map of all the properties of this record type.
* @throws IllegalStateException if the {@code RecordProperty} associated
* with a property is null.
*/
RecordType(JSTypeRegistry registry, Map<String, RecordProperty> properties) {
super(registry, null, null);
for (String property : properties.keySet()) {
RecordProperty prop = properties.get(property);
if (prop == null) {
throw new IllegalStateException(
"RecordProperty associated with a property should not be null!");
}
defineDeclaredProperty(property, prop.getType(), false, prop.getPropertyNode());
}
// Freeze the record type.
isFrozen = true;
}
@Override
public boolean isEquivalentTo(JSType other) {
if (!(other instanceof RecordType)) {
return false;
}
// Compare properties.
RecordType otherRecord = (RecordType) other;
Set<String> keySet = properties.keySet();
Map<String, JSType> otherProps = otherRecord.properties;
if (!otherProps.keySet().equals(keySet)) {
return false;
}
for (String key : keySet) {
if (!otherProps.get(key).isEquivalentTo(properties.get(key))) {
return false;
}
}
return true;
}
@Override
public ObjectType getImplicitPrototype() {
return registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE);
}
@Override
boolean defineProperty(String propertyName, JSType type,
boolean inferred, boolean inExterns, Node propertyNode) {
if (isFrozen) {
return false;
}
if (!inferred) {
properties.put(propertyName, type);
}
return super.defineProperty(propertyName, type, inferred, inExterns,
propertyNode);
}
@Override
public JSType getLeastSupertype(JSType that) {
if (!that.isRecordType()) {
return super.getLeastSupertype(that);
}
RecordType thatRecord = (RecordType) that;
RecordTypeBuilder builder = new RecordTypeBuilder(registry);
// The least supertype consist of those properties of the record
// type that both record types hold in common both by name and
// type of the properties themselves.
for (String property : properties.keySet()) {
if (thatRecord.hasProperty(property) &&
thatRecord.getPropertyType(property).isEquivalentTo(
getPropertyType(property))) {
builder.addProperty(property, getPropertyType(property),
getPropertyNode(property));
}
}
return builder.build();
}
@Override
public JSType getGreatestSubtype(JSType that) {
if (that.isRecordType()) {
RecordType thatRecord = (RecordType) that;
RecordTypeBuilder builder = new RecordTypeBuilder(registry);
// The greatest subtype consists of those *unique* properties of both
// record types. If any property conflicts, then the NO_TYPE type
// is returned.
for (String property : properties.keySet()) {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import static com.google.javascript.rhino.jstype.JSTypeNative.GLOBAL_THIS;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import com.google.javascript.rhino.ErrorReporter;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.ObjectType;
import com.google.javascript.rhino.jstype.StaticScope;
import com.google.javascript.rhino.jstype.StaticSlot;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Scope contains information about a variable scope in javascript.
* Scopes can be nested, a scope points back to its parent scope.
* A Scope contains information about variables defined in that scope.
* <p>
* A Scope is also used as a lattice element for flow-sensitive type inference.
* As a lattice element, a Scope is viewed as a map from names to types. A name
* not in the map is considered to have the bottom type. The join of two maps m1
* and m2 is the map of the union of names with {@link JSType#getLeastSupertype}
* to meet the m1 type and m2 type.
*
* @see NodeTraversal
* @see DataFlowAnalysis
*
*/
public class Scope implements StaticScope<JSType> {
private final Map<String, Var> vars = new LinkedHashMap<String, Var>();
private final Scope parent;
private final int depth;
private final Node rootNode;
/** The type of {@code this} in the current scope. */
private final ObjectType thisType;
/** Whether this is a bottom scope for the purposes of type inference. */
private final boolean isBottom;
private Var arguments;
private static final Predicate<Var> DECLARATIVELY_UNBOUND_VARS_WITHOUT_TYPES =
new Predicate<Var>() {
@Override public boolean apply(Var var) {
return var.getParentNode() != null &&
var.getType() == null && // no
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> declared type
var.getParentNode().getType() == Token.VAR &&
!var.isExtern();
}
};
/** Stores info about a variable */
public static class Var implements StaticSlot<JSType> {
/** name */
final String name;
/** Var node */
final Node nameNode;
/**
* The variable's type.
*/
private JSType type;
/**
* The variable's doc info.
*/
private final JSDocInfo info;
/**
* Whether the variable's type has been inferred or is declared. An inferred
* type may change over time (as more code is discovered), whereas a
* declared type is a static contract that must be matched.
*/
private final boolean typeInferred;
/** Input source */
final CompilerInput input;
/** Whether the variable is a define */
final boolean isDefine;
/**
* The index at which the var is declared. e..g if it's 0, it's the first
* declared variable in that scope
*/
final int index;
/** The enclosing scope */
final Scope scope;
/**
* Creates a variable.
*
* @param inferred whether its type is inferred (as opposed to declared)
*/
private Var(boolean inferred, String name, Node nameNode, JSType type,
Scope scope, int index, CompilerInput input, boolean isDefine,
JSDocInfo info) {
this.name = name;
this.nameNode = nameNode;
this.type = type;
this.scope = scope;
this.index = index;
this.input = input;
this.isDefine = isDefine;
this.info = info;
this.typeInferred = inferred;
}
/**
* Gets the name of the variable.
*/
public String getName() {
return name;
}
/**
* Gets the parent of the name node.
*/
public Node getParentNode() {
return nameNode == null ? null : nameNode.getParent();
}
/**
* Whether this is a bleeding function (an anonymous named function
* that bleeds into the inner scope.
*/
public boolean isBleedingFunction() {
return NodeUtil.isFunctionExpression(getParentNode());
}
/**
* Gets the scope where this variable is declared.
*/
Scope getScope() {
return scope;
}
/**
* Returns whether this is a global variable.
*/
public boolean isGlobal() {
return scope.isGlobal();
}
/**
* Returns whether this is a local variable.
*/
public boolean isLocal() {
return scope.isLocal();
}
/**
* Returns whether this is defined in an extern file.
*/
boolean isExtern() {
return input == null || input.isExtern();
}
/**
* Returns {@code true} if the variable is declared as a constant,
* based on the value reported by {@code NodeUtil}.
*/
public boolean isConst() {
return nameNode != null && NodeUtil.isConstantName(nameNode);
}
/**
* Returns {@code true} if the variable is declared as a define.
* A variable is a define if it is annot
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>aed by {@code @define}.
*/
public boolean isDefine() {
return isDefine;
}
public Node getInitialValue() {
Node parent = getParentNode();
int pType = parent.getType();
if (pType == Token.FUNCTION) {
return parent;
} else if (pType == Token.ASSIGN) {
return parent.getLastChild();
} else if (pType == Token.VAR) {
return nameNode.getFirstChild();
} else {
return null;
}
}
/**
* Gets this variable's type. To know whether this type has been inferred,
* see {@code #isTypeInferred()}.
*/
public JSType getType() {
return type;
}
/**
* Returns the name node that produced this variable.
*/
public Node getNameNode() {
return nameNode;
}
/**
* Gets the JSDocInfo for the variable.
*/
public JSDocInfo getJSDocInfo() {
return info;
}
/**
* Sets this variable's type.
* @throws IllegalStateException if the variable's type is not inferred
*/
void setType(JSType type) {
Preconditions.checkState(isTypeInferred());
this.type = type;
}
/**
* Resolve this variable's type.
*/
void resolveType(ErrorReporter errorReporter) {
if (type != null) {
type = type.resolve(errorReporter, scope);
}
}
/**
* Returns whether this variable's type is inferred. To get the variable's
* type, see {@link #getType()}.
*/
public boolean isTypeInferred() {
return typeInferred;
}
public String getInputName() {
if (input == null)
return "<non-file>";
else
return input.getName();
}
public boolean isNoShadow() {
if (info != null && info.isNoShadow()) {
return true;
} else {
return false;
}
}
@Override public boolean equals(Object other) {
if (!(other instanceof Var)) {
return false;
}
Var otherVar = (Var) other;
return otherVar.nameNode == nameNode;
}
@Override public int hashCode() {
return nameNode.hashCode();
}
@Override
public String toString() {
return "Scope.Var " + name + "{" + type + "}";
}
}
/**
* A special subclass of Var used to distinguish "arguments" in the current
* scope.
*/
// TODO(johnlenz): Include this the list of Vars for the scope.
public static class Arguments extends Var {
Arguments(Scope scope) {
super(
false, // no inferred
"arguments", // always arguments
null, // no declaration node
// TODO(johnlenz): provide the type of "Arguments".
null, // no type info
scope,
-1, // no variable index
null, // input,
false, // not a define
null // no jsdoc
);
}
@Override public boolean equals(Object other) {
if (!(other instanceof Arguments)) {
return false;
}
Arguments other
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>Var = (Arguments) other;
return otherVar.scope.getRootNode() == scope.getRootNode();
}
@Override public int hashCode() {
return System.identityHashCode(this);
}
}
/**
* Creates a Scope given the parent Scope and the root node of the scope.
* @param parent The parent Scope. Cannot be null.
* @param rootNode Typically the FUNCTION node.
*/
Scope(Scope parent, Node rootNode) {
Preconditions.checkNotNull(parent);
Preconditions.checkArgument(rootNode != parent.rootNode);
this.parent = parent;
this.rootNode = rootNode;
JSType nodeType = rootNode.getJSType();
if (nodeType != null && nodeType instanceof FunctionType) {
thisType = ((FunctionType) nodeType).getTypeOfThis();
} else {
thisType = parent.thisType;
}
this.isBottom = false;
this.depth = parent.depth + 1;
}
/**
* Creates a global Scope.
* @param rootNode Typically the global BLOCK node.
*/
Scope(Node rootNode, AbstractCompiler compiler) {
this.parent = null;
this.rootNode = rootNode;
thisType = compiler.getTypeRegistry().getNativeObjectType(GLOBAL_THIS);
this.isBottom = false;
this.depth = 0;
}
/**
* Creates a empty Scope (bottom of the lattice).
* @param rootNode Typically a FUNCTION node or the global BLOCK node.
* @param thisType the type of {@code this} in this scope
*/
Scope(Node rootNode, ObjectType thisType) {
this.parent = null;
this.rootNode = rootNode;
this.thisType = thisType;
this.isBottom = true;
this.depth = 0;
}
/** The depth of the scope. The global scope has depth 0. */
int getDepth() {
return depth;
}
/** Whether this is the bottom of the lattice. */
boolean isBottom() {
return isBottom;
}
/**
* Gets the container node of the scope. This is typically the FUNCTION
* node or the global BLOCK/SCRIPT node.
*/
public Node getRootNode() {
return rootNode;
}
public Scope getParent() {
return parent;
}
Scope getGlobalScope() {
Scope result = this;
while (result.getParent() != null) {
result = result.getParent();
}
return result;
}
@Override
public StaticScope<JSType> getParentScope() {
return parent;
}
/**
* Gets the type of {@code this} in the current scope.
*/
public ObjectType getTypeOfThis() {
return thisType;
}
/**
* Declares a variable whose type is inferred.
*
* @param name name of the variable
* @param nameNode the NAME node declaring the variable
* @param type the variable's type
* @param input the input in which this variable is defined.
*/
Var declare(String name, Node nameNode, JSType type, CompilerInput input) {
return declare(name, nameNode, type, input
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>_ERROR =
DiagnosticType.error(
"JSC_NON_LITERAL_TWEAK_ID_ERROR",
"tweak ID must be a string literal");
static final DiagnosticType INVALID_TWEAK_DEFAULT_VALUE_WARNING =
DiagnosticType.warning(
"JSC_INVALID_TWEAK_DEFAULT_VALUE_WARNING",
"tweak {0} registered with {1} must have a default value that is a " +
"literal of type {2}");
static final DiagnosticType NON_GLOBAL_TWEAK_INIT_ERROR =
DiagnosticType.error(
"JSC_NON_GLOBAL_TWEAK_INIT_ERROR",
"tweak declaration {0} must occur in the global scope");
static final DiagnosticType TWEAK_OVERRIDE_AFTER_REGISTERED_ERROR =
DiagnosticType.error(
"JSC_TWEAK_OVERRIDE_AFTER_REGISTERED_ERROR",
"Cannot override the default value of tweak {0} after it has been " +
"registered");
static final DiagnosticType TWEAK_WRONG_GETTER_TYPE_WARNING =
DiagnosticType.warning(
"JSC_TWEAK_WRONG_GETTER_TYPE_WARNING",
"tweak getter function {0} used for tweak registered using {1}");
static final DiagnosticType INVALID_TWEAK_ID_ERROR =
DiagnosticType.error(
"JSC_INVALID_TWEAK_ID_ERROR",
"tweak ID contains illegal characters. Only letters, numbers, _ " +
"and . are allowed");
/**
* An enum of goog.tweak functions.
*/
private static enum TweakFunction {
REGISTER_BOOLEAN("goog.tweak.registerBoolean", "boolean", Token.TRUE,
Token.FALSE),
REGISTER_NUMBER("goog.tweak.registerNumber", "number", Token.NUMBER),
REGISTER_STRING("goog.tweak.registerString", "string", Token.STRING),
OVERRIDE_DEFAULT_VALUE("goog.tweak.overrideDefaultValue"),
GET_COMPILER_OVERRIDES("goog.tweak.getCompilerOverrides_"),
GET_BOOLEAN("goog.tweak.getBoolean", REGISTER_BOOLEAN),
GET_NUMBER("goog.tweak.getNumber", REGISTER_NUMBER),
GET_STRING("goog.tweak.getString", REGISTER_STRING);
final String name;
final String expectedTypeName;
final int validNodeTypeA;
final int validNodeTypeB;
final TweakFunction registerFunction;
TweakFunction(String name) {
this(name, null, Token.ERROR, Token.ERROR, null);
}
TweakFunction(String name, String expectedTypeName,
int validNodeTypeA) {
this(name, expectedTypeName, validNodeTypeA, Token.ERROR, null);
}
TweakFunction(String name, String expectedTypeName,
int validNodeTypeA, int validNodeTypeB) {
this(name, expectedTypeName, validNodeTypeA, validNodeTypeB, null);
}
TweakFunction(String name, TweakFunction registerFunction) {
this(name, null, Token.ERROR, Token.ERROR, registerFunction);
}
TweakFunction(String name, String expectedTypeName,
int validNodeTypeA, int validNodeType
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>B,
TweakFunction registerFunction) {
this.name = name;
this.expectedTypeName = expectedTypeName;
this.validNodeTypeA = validNodeTypeA;
this.validNodeTypeB = validNodeTypeB;
this.registerFunction = registerFunction;
}
boolean isValidNodeType(int type) {
return type == validNodeTypeA || type == validNodeTypeB;
}
boolean isCorrectRegisterFunction(TweakFunction registerFunction) {
Preconditions.checkNotNull(registerFunction);
return this.registerFunction == registerFunction;
}
boolean isGetterFunction() {
return registerFunction != null;
}
String getName() {
return name;
}
String getExpectedTypeName() {
return expectedTypeName;
}
Node createDefaultValueNode() {
switch (this) {
case REGISTER_BOOLEAN:
return new Node(Token.FALSE);
case REGISTER_NUMBER:
return Node.newNumber(0);
case REGISTER_STRING:
return Node.newString("");
}
throw new IllegalStateException();
}
}
// A map of function name -> TweakFunction.
private static final Map<String, TweakFunction> TWEAK_FUNCTIONS_MAP;
static {
TWEAK_FUNCTIONS_MAP = Maps.newHashMap();
for (TweakFunction func : TweakFunction.values()) {
TWEAK_FUNCTIONS_MAP.put(func.getName(), func);
}
}
ProcessTweaks(AbstractCompiler compiler, boolean stripTweaks,
Map<String, Node> compilerDefaultValueOverrides) {
this.compiler = compiler;
this.stripTweaks = stripTweaks;
// Having the map sorted is required for the unit tests to be deterministic.
this.compilerDefaultValueOverrides = Maps.newTreeMap();
this.compilerDefaultValueOverrides.putAll(compilerDefaultValueOverrides);
}
@Override
public void process(Node externs, Node root) {
CollectTweaksResult result = collectTweaks(root);
applyCompilerDefaultValueOverrides(result.tweakInfos);
boolean changed = false;
if (stripTweaks) {
changed = stripAllCalls(result.tweakInfos);
} else if (!compilerDefaultValueOverrides.isEmpty()) {
changed = replaceGetCompilerOverridesCalls(result.getOverridesCalls);
}
if (changed) {
compiler.reportCodeChange();
}
}
/**
* Passes the compiler default value overrides to the JS by replacing calls
* to goog.tweak.getCompilerOverrids_ with a map of tweak ID->default value;
*/
private boolean replaceGetCompilerOverridesCalls(
List<TweakFunctionCall> calls) {
for (TweakFunctionCall call : calls) {
Node callNode = call.callNode;
Node objNode = createCompilerDefaultValueOverridesVarNode(callNode);
callNode.getParent().replaceChild(callNode, objNode);
}
return !calls.isEmpty();
}
/**
* Removes all CALL nodes in the given TweakInfos, replacing calls to getter
* functions with the tweak's default value.
*/
private boolean stripAllCalls(Map<String, TweakInfo> tweakInfos) {
for (TweakInfo tweakInfo :
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> tweakInfos.values()) {
boolean isRegistered = tweakInfo.isRegistered();
for (TweakFunctionCall functionCall : tweakInfo.functionCalls) {
Node callNode = functionCall.callNode;
Node parent = callNode.getParent();
if (functionCall.tweakFunc.isGetterFunction()) {
Node newValue;
if (isRegistered) {
newValue = tweakInfo.getDefaultValueNode().cloneNode();
} else {
// When we find a getter of an unregistered tweak, there has
// already been a warning about it, so now just use a default
// value when stripping.
TweakFunction registerFunction =
functionCall.tweakFunc.registerFunction;
newValue = registerFunction.createDefaultValueNode();
}
parent.replaceChild(callNode, newValue);
} else {
Node voidZeroNode = new Node(Token.VOID)
.copyInformationFrom(callNode);
voidZeroNode.addChildToBack(Node.newNumber(0)
.copyInformationFrom(callNode));
parent.replaceChild(callNode, voidZeroNode);
}
}
}
return !tweakInfos.isEmpty();
}
/**
* Creates a JS object that holds a map of tweakId -> default value override.
*/
private Node createCompilerDefaultValueOverridesVarNode(
Node sourceInformationNode) {
Node objNode = new Node(Token.OBJECTLIT)
.copyInformationFrom(sourceInformationNode);
for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) {
Node objKeyNode = Node.newString(entry.getKey())
.copyInformationFrom(sourceInformationNode);
Node objValueNode = entry.getValue().cloneNode()
.copyInformationFrom(sourceInformationNode);
objKeyNode.addChildToBack(objValueNode);
objNode.addChildToBack(objKeyNode);
}
return objNode;
}
/** Sets the default values of tweaks based on compiler options. */
private void applyCompilerDefaultValueOverrides(
Map<String, TweakInfo> tweakInfos) {
for (Entry<String, Node> entry : compilerDefaultValueOverrides.entrySet()) {
String tweakId = entry.getKey();
TweakInfo tweakInfo = tweakInfos.get(tweakId);
if (tweakInfo == null) {
compiler.report(JSError.make(UNKNOWN_TWEAK_WARNING, tweakId));
} else {
TweakFunction registerFunc = tweakInfo.registerCall.tweakFunc;
Node value = entry.getValue();
if (!registerFunc.isValidNodeType(value.getType())) {
compiler.report(JSError.make(INVALID_TWEAK_DEFAULT_VALUE_WARNING,
tweakId, registerFunc.getName(),
registerFunc.getExpectedTypeName()));
} else {
tweakInfo.defaultValueNode = value;
}
}
}
}
/**
* Finds all calls to goog.tweak functions and emits warnings/errors if any
* of the calls have issues.
* @return A map of {@link TweakInfo} structures, keyed by tweak ID.
*/
private CollectTweaksResult collectTweaks(Node root) {
CollectTweaks pass = new CollectTweaks();
NodeTraversal.
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>traverse(compiler, root, pass);
Map<String, TweakInfo> tweakInfos = pass.allTweaks;
for (TweakInfo tweakInfo: tweakInfos.values()) {
tweakInfo.emitAllWarnings();
}
return new CollectTweaksResult(tweakInfos, pass.getOverridesCalls);
}
private final static class CollectTweaksResult {
final Map<String, TweakInfo> tweakInfos;
final List<TweakFunctionCall> getOverridesCalls;
CollectTweaksResult(Map<String, TweakInfo> tweakInfos,
List<TweakFunctionCall> getOverridesCalls) {
this.tweakInfos = tweakInfos;
this.getOverridesCalls = getOverridesCalls;
}
}
/**
* Processes all calls to goog.tweak functions.
*/
private final class CollectTweaks extends AbstractPostOrderCallback {
final Map<String, TweakInfo> allTweaks = Maps.newHashMap();
final List<TweakFunctionCall> getOverridesCalls = Lists.newArrayList();
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() != Token.CALL) {
return;
}
String callName = n.getFirstChild().getQualifiedName();
TweakFunction tweakFunc = TWEAK_FUNCTIONS_MAP.get(callName);
if (tweakFunc == null) {
return;
}
if (tweakFunc == TweakFunction.GET_COMPILER_OVERRIDES) {
getOverridesCalls.add(
new TweakFunctionCall(t.getSourceName(), tweakFunc, n));
return;
}
// Ensure the first parameter (the tweak ID) is a string literal.
Node tweakIdNode = n.getFirstChild().getNext();
if (tweakIdNode.getType() != Token.STRING) {
compiler.report(t.makeError(tweakIdNode, NON_LITERAL_TWEAK_ID_ERROR));
return;
}
String tweakId = tweakIdNode.getString();
// Make sure there is a TweakInfo structure for it.
TweakInfo tweakInfo = allTweaks.get(tweakId);
if (tweakInfo == null) {
tweakInfo = new TweakInfo(tweakId);
allTweaks.put(tweakId, tweakInfo);
}
switch (tweakFunc) {
case REGISTER_BOOLEAN:
case REGISTER_NUMBER:
case REGISTER_STRING:
// Ensure the ID contains only valid characters.
if (!ID_MATCHER.matchesAllOf(tweakId)) {
compiler.report(t.makeError(tweakIdNode, INVALID_TWEAK_ID_ERROR));
}
// Ensure tweaks are registered in the global scope.
if (!t.inGlobalScope()) {
compiler.report(
t.makeError(n, NON_GLOBAL_TWEAK_INIT_ERROR, tweakId));
break;
}
// Ensure tweaks are registered only once.
if (tweakInfo.isRegistered()) {
compiler.report(
t.makeError(n, TWEAK_MULTIPLY_REGISTERED_ERROR, tweakId));
break;
}
Node
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>(valueNode.getType())) {
compiler.report(JSError.make(call.sourceName,
valueNode, INVALID_TWEAK_DEFAULT_VALUE_WARNING,
tweakId, registerFunc.getName(),
registerFunc.getExpectedTypeName()));
}
} else if (tweakFunc.isGetterFunction()) {
// For getter calls, ensure the correct getter was used.
if (!tweakFunc.isCorrectRegisterFunction(registerFunc)) {
compiler.report(JSError.make(call.sourceName,
call.callNode, TWEAK_WRONG_GETTER_TYPE_WARNING,
tweakFunc.getName(), registerFunc.getName()));
}
}
}
}
/**
* Emits an error for each function call that was found.
*/
void emitUnknownTweakErrors() {
for (TweakFunctionCall call : functionCalls) {
compiler.report(JSError.make(call.sourceName,
call.getIdNode(), UNKNOWN_TWEAK_WARNING, tweakId));
}
}
void addRegisterCall(String sourceName, TweakFunction tweakFunc,
Node callNode, Node defaultValueNode) {
registerCall = new TweakFunctionCall(sourceName, tweakFunc, callNode,
defaultValueNode);
functionCalls.add(registerCall);
}
void addOverrideDefaultValueCall(String sourceName,
TweakFunction tweakFunc, Node callNode, Node defaultValueNode) {
functionCalls.add(new TweakFunctionCall(sourceName, tweakFunc, callNode,
defaultValueNode));
this.defaultValueNode = defaultValueNode;
}
void addGetterCall(String sourceName, TweakFunction tweakFunc,
Node callNode) {
functionCalls.add(new TweakFunctionCall(sourceName, tweakFunc, callNode));
}
boolean isRegistered() {
return registerCall != null;
}
Node getDefaultValueNode() {
Preconditions.checkState(isRegistered());
// Use calls to goog.tweak.overrideDefaultValue() first.
if (defaultValueNode != null) {
return defaultValueNode;
}
// Use the value passed to the register function next.
if (registerCall.valueNode != null) {
return registerCall.valueNode;
}
// Otherwise, use the default value for the tweak's type.
return registerCall.tweakFunc.createDefaultValueNode();
}
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> Node functionBlock) {
if (functionBlock == null || compiler.getInput(sourceName).isExtern()) {
return this;
}
Preconditions.checkArgument(functionBlock.getType() == Token.BLOCK);
if (returnType == null) {
boolean hasNonEmptyReturns = false;
List<Node> worklist = Lists.newArrayList(functionBlock);
while (!worklist.isEmpty()) {
Node current = worklist.remove(worklist.size() - 1);
int cType = current.getType();
if (cType == Token.RETURN && current.getFirstChild() != null) {
hasNonEmptyReturns = true;
break;
} else if (NodeUtil.isStatementBlock(current) ||
NodeUtil.isControlStructure(current)) {
for (Node child = current.getFirstChild();
child != null; child = child.getNext()) {
worklist.add(child);
}
}
}
if (!hasNonEmptyReturns) {
returnType = typeRegistry.getNativeType(VOID_TYPE);
returnTypeInferred = true;
}
}
return this;
}
/**
* Infer the role of the function (whether it's a constructor or interface)
* and what it inherits from in JSDocInfo.
*/
FunctionTypeBuilder inferInheritance(@Nullable JSDocInfo info) {
if (info != null) {
isConstructor = info.isConstructor();
isInterface = info.isInterface();
// base type
if (info.hasBaseType()) {
if (isConstructor) {
JSType maybeBaseType =
info.getBaseType().evaluate(scope, typeRegistry);
if (maybeBaseType != null &&
maybeBaseType.setValidator(new ExtendedTypeValidator())) {
baseType = (ObjectType) maybeBaseType;
}
} else {
reportWarning(EXTENDS_WITHOUT_TYPEDEF, fnName);
}
}
// implemented interfaces
if (isConstructor || isInterface) {
implementedInterfaces = Lists.newArrayList();
for (JSTypeExpression t : info.getImplementedInterfaces()) {
JSType maybeInterType = t.evaluate(scope, typeRegistry);
if (maybeInterType != null &&
maybeInterType.setValidator(new ImplementedTypeValidator())) {
implementedInterfaces.add((ObjectType) maybeInterType);
}
}
} else if (info.getImplementedInterfaceCount() > 0) {
reportWarning(IMPLEMENTS_WITHOUT_CONSTRUCTOR, fnName);
}
// extended interfaces (for interface only)
if (isInterface) {
extendedInterfaces = Lists.newArrayList();
for (JSTypeExpression t : info.getExtendedInterfaces()) {
JSType maybeInterfaceType = t.evaluate(scope, typeRegistry);
if (maybeInterfaceType != null &&
maybeInterfaceType.setValidator(new ExtendedTypeValidator())) {
extendedInterfaces.add((ObjectType) maybeInterfaceType);
}
}
}
}
return this;
}
/**
* Infers the type of {@code this}.
* @param type The type of this.
*/
FunctionTypeBuilder inferThisType(JSDocInfo info, JSType type) {
// Look at the @this annotation first.
inferThisType
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>(info, (Node) null);
if (thisType == null) {
ObjectType objType = ObjectType.cast(type);
if (objType != null && (info == null || !info.hasType())) {
thisType = objType;
}
}
return this;
}
/**
* Infers the type of {@code this}.
* @param info The JSDocInfo for this function.
* @param owner The node for the object whose prototype "owns" this function.
* For example, {@code A} in the expression {@code A.prototype.foo}. May
* be null to indicate that this is not a prototype property.
*/
FunctionTypeBuilder inferThisType(JSDocInfo info,
@Nullable Node owner) {
ObjectType maybeThisType = null;
if (info != null && info.hasThisType()) {
maybeThisType = ObjectType.cast(
info.getThisType().evaluate(scope, typeRegistry));
}
if (maybeThisType != null) {
thisType = maybeThisType;
thisType.setValidator(new ThisTypeValidator());
} else if (owner != null &&
(info == null || !info.hasType())) {
// If the function is of the form:
// x.prototype.y = function() {}
// then we can assume "x" is the @this type. On the other hand,
// if it's of the form:
// /** @type {Function} */ x.prototype.y;
// then we should not give it a @this type.
String ownerTypeName = owner.getQualifiedName();
Var ownerVar = scope.getVar(ownerTypeName);
JSType ownerType = ownerVar == null ? null : ownerVar.getType();
FunctionType ownerFnType = ownerType instanceof FunctionType ?
(FunctionType) ownerType : null;
ObjectType instType =
ownerFnType == null || ownerFnType.isOrdinaryFunction() ?
null : ownerFnType.getInstanceType();
if (instType != null) {
thisType = instType;
}
}
return this;
}
/**
* Infer the parameter types from the doc info alone.
*/
FunctionTypeBuilder inferParameterTypes(JSDocInfo info) {
// Create a fake args parent.
Node lp = new Node(Token.LP);
for (String name : info.getParameterNames()) {
lp.addChildToBack(Node.newString(Token.NAME, name));
}
return inferParameterTypes(lp, info);
}
/**
* Infer the parameter types from the list of argument names and
* the doc info.
*/
FunctionTypeBuilder inferParameterTypes(@Nullable Node argsParent,
@Nullable JSDocInfo info) {
if (argsParent == null) {
if (info == null) {
return this;
} else {
return inferParameterTypes(info);
}
}
// arguments
Node oldParameterType = null;
if (parametersNode != null) {
oldParameterType = parametersNode.getFirstChild();
}
FunctionParamBuilder builder = new FunctionParamBuilder(typeRegistry);
boolean warnedAboutArgList = false;
Set<String> all
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
fnType.setPrototypeBasedOn(baseType);
}
}
/**
* Returns a constructor function either by returning it from the
* registry if it exists or creating and registering a new type. If
* there is already a type, then warn if the existing type is
* different than the one we are creating, though still return the
* existing function if possible. The primary purpose of this is
* that registering a constructor will fail for all built-in types
* that are initialized in {@link JSTypeRegistry}. We a) want to
* make sure that the type information specified in the externs file
* matches what is in the registry and b) annotate the externs with
* the {@link JSType} from the registry so that there are not two
* separate JSType objects for one type.
*/
private FunctionType getOrCreateConstructor() {
FunctionType fnType = typeRegistry.createConstructorType(
fnName, sourceNode, parametersNode, returnType);
JSType existingType = typeRegistry.getType(fnName);
if (existingType != null) {
boolean isInstanceObject = existingType instanceof InstanceObjectType;
if (isInstanceObject || fnName.equals("Function")) {
FunctionType existingFn =
isInstanceObject ?
((InstanceObjectType) existingType).getConstructor() :
typeRegistry.getNativeFunctionType(FUNCTION_FUNCTION_TYPE);
if (existingFn.getSource() == null) {
existingFn.setSource(sourceNode);
}
if (!existingFn.hasEqualCallType(fnType)) {
reportWarning(TYPE_REDEFINITION, fnName,
fnType.toString(), existingFn.toString());
}
return existingFn;
} else {
// We fall through and return the created type, even though it will fail
// to register. We have no choice as we have to return a function. We
// issue an error elsewhere though, so the user should fix it.
}
}
maybeSetBaseType(fnType);
if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) {
typeRegistry.declareType(fnName, fnType.getInstanceType());
}
return fnType;
}
private void reportWarning(DiagnosticType warning, String ... args) {
compiler.report(JSError.make(sourceName, errorRoot, warning, args));
}
private void reportError(DiagnosticType error, String ... args) {
compiler.report(JSError.make(sourceName, errorRoot, error, args));
}
/**
* Determines whether the given jsdoc info declares a function type.
*/
static boolean isFunctionTypeDeclaration(JSDocInfo info) {
return info.getParameterCount() > 0 ||
info.hasReturnType() ||
info.hasThisType() ||
info.isConstructor() ||
info.isInterface();
}
/**
* The scope that we should declare this function in, if it needs
* to be declared in a scope. Notice that TypedScopeCreator takes
* care of most scope-declaring.
*/
private Scope getScopeDeclaredIn() {
int dotIndex = fnName.indexOf(".");
if (dotIndex != -1) {
String rootVarName = fnName
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>, it's
* undefined, and any references to descendant names should emit warnings.
*/
private void checkDescendantNames(Name name, boolean nameIsDefined) {
if (name.props != null) {
for (Name prop : name.props) {
// if the ancestor of a property is not defined, then we should emit
// warnings for all references to the property.
boolean propIsDefined = false;
if (nameIsDefined) {
// if the ancestor of a property is defined, then let's check that
// the property is also explicitly defined if it needs to be.
propIsDefined = (!propertyMustBeInitializedByFullName(prop) ||
prop.globalSets + prop.localSets > 0);
}
validateName(prop, propIsDefined);
checkDescendantNames(prop, propIsDefined);
}
}
}
private void validateName(Name name, boolean isDefined) {
// If the name is not defined, emit warnings for each reference. While
// we're looking through each reference, check all the module dependencies.
Ref declaration = name.declaration;
Name parent = name.parent;
boolean singleGlobalParentDecl =
parent != null &&
parent.declaration != null &&
parent.localSets == 0;
JSModuleGraph moduleGraph = compiler.getModuleGraph();
for (Ref ref : name.getRefs()) {
if (!isDefined && !isTypedef(ref)) {
reportRefToUndefinedName(name, ref);
} else if (declaration != null &&
ref.getModule() != declaration.getModule() &&
!moduleGraph.dependsOn(
ref.getModule(), declaration.getModule())) {
reportBadModuleReference(name, ref);
} else if (ref.scope.isGlobal() &&
singleGlobalParentDecl &&
parent.declaration.preOrderIndex > ref.preOrderIndex) {
compiler.report(
JSError.make(ref.source.getName(), ref.node,
NAME_DEFINED_LATE_WARNING,
name.fullName(),
parent.fullName(),
parent.declaration.source.getName(),
String.valueOf(parent.declaration.node.getLineno())));
}
}
}
private boolean isTypedef(Ref ref) {
// If this is an annotated EXPR-GET, don't do anything.
Node parent = ref.node.getParent();
if (parent.getType() == Token.EXPR_RESULT) {
JSDocInfo info = ref.node.getJSDocInfo();
if (info != null && info.hasTypedefType()) {
return true;
}
}
return false;
}
private void reportBadModuleReference(Name name, Ref ref) {
compiler.report(
JSError.make(ref.source.getName(), ref.node, STRICT_MODULE_DEP_QNAME,
ref.getModule().getName(),
name.declaration.getModule().getName(),
name.fullName()));
}
private void reportRefToUndefinedName(Name name, Ref ref) {
// grab the highest undefined ancestor to output in the warning message.
while (name.parent != null &&
name.parent.globalSets + name.parent.localSets == 0) {
name =
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>.prototype, SuperClass.prototype)
// goog$mixin(SubClass.prototype, SuperClass.prototype)
boolean isDeprecatedCall = callNode.getChildCount() == 2 &&
callName.getType() == Token.GETPROP;
if (isDeprecatedCall) {
// SubClass.inherits(SuperClass)
subclass = callName.getFirstChild();
} else if (callNode.getChildCount() == 3) {
// goog.inherits(SubClass, SuperClass)
subclass = callName.getNext();
}
if (type == SubclassType.MIXIN) {
// Only consider mixins that mix two prototypes as related to
// inheritance.
if (!endsWithPrototype(superclass)) {
return null;
}
if (!isDeprecatedCall) {
if (!endsWithPrototype(subclass)) {
return null;
}
// Strip off the prototype from the name.
subclass = subclass.getFirstChild();
}
superclass = superclass.getFirstChild();
}
// bail out if either of the side of the "inherits"
// isn't a real class name. This prevents us from
// doing something weird in cases like:
// goog.inherits(MySubClass, cond ? SuperClass1 : BaseClass2)
if (subclass != null &&
subclass.isUnscopedQualifiedName() &&
superclass.isUnscopedQualifiedName()) {
return new SubclassRelationship(type, subclass, superclass);
}
}
return null;
}
/**
* Determines whether the given node is a class-defining name, like
* "inherits" or "mixin."
* @return The type of class-defining name, or null.
*/
private SubclassType typeofClassDefiningName(Node callName) {
// Check if the method name matches one of the class-defining methods.
String methodName = null;
if (callName.getType() == Token.GETPROP) {
methodName = callName.getLastChild().getString();
} else if (callName.getType() == Token.NAME) {
String name = callName.getString();
int dollarIndex = name.lastIndexOf('$');
if (dollarIndex != -1) {
methodName = name.substring(dollarIndex + 1);
}
}
if (methodName != null) {
if (methodName.equals("inherits")) {
return SubclassType.INHERITS;
} else if (methodName.equals("mixin")) {
return SubclassType.MIXIN;
}
}
return null;
}
@Override
public boolean isSuperClassReference(String propertyName) {
return "superClass_".equals(propertyName);
}
/**
* Given a qualified name node, returns whether "prototype" is at the end.
* For example:
* a.b.c => false
* a.b.c.prototype => true
*/
private boolean endsWithPrototype(Node qualifiedName) {
return qualifiedName.getType() == Token.GETPROP &&
qualifiedName.getLastChild().getString().equals("prototype");
}
/**
* Exctracts X from goog.provide('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
public String extractClassNameIfProvide(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.provide");
}
/**
* Exctracts X from goog.require('X'), if the applied Node is goog.
*
* @return The extracted class name, or null.
*/
@Override
public String extractClassNameIfRequire(Node node, Node parent){
return extractClassNameIfGoog(node, parent, "goog.require");
}
private static String extractClassNameIfGoog(Node node, Node parent,
String functionName){
String className = null;
if (NodeUtil.isExprCall(parent)) {
Node callee = node.getFirstChild();
if (callee != null && callee.getType() == Token.GETPROP) {
String qualifiedName = callee.getQualifiedName();
if ((functionName).equals(qualifiedName)) {
className = callee.getNext().getString();
}
}
}
return className;
}
/**
* Use closure's implementation.
* @return closure's function name for exporting properties.
*/
@Override
public String getExportPropertyFunction() {
return "goog.exportProperty";
}
/**
* Use closure's implementation.
* @return closure's function name for exporting symbols.
*/
@Override
public String getExportSymbolFunction() {
return "goog.exportSymbol";
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
Node callName = n.getFirstChild();
if ("goog.addDependency".equals(callName.getQualifiedName()) &&
n.getChildCount() >= 3) {
Node typeArray = callName.getNext().getNext();
if (typeArray.getType() == Token.ARRAYLIT) {
List<String> typeNames = Lists.newArrayList();
for (Node name = typeArray.getFirstChild(); name != null;
name = name.getNext()) {
if (name.getType() == Token.STRING) {
typeNames.add(name.getString());
}
}
return typeNames;
}
}
return null;
}
@Override
public String identifyTypeDefAssign(Node n) {
Node firstChild = n.getFirstChild();
int type = n.getType();
if (type == Token.ASSIGN) {
if (TYPEDEF_NAME.equals(n.getLastChild().getQualifiedName())) {
return firstChild.getQualifiedName();
}
} else if (type == Token.VAR && firstChild.hasChildren()) {
if (TYPEDEF_NAME.equals(
firstChild.getFirstChild().getQualifiedName())) {
return firstChild.getString();
}
}
return null;
}
@Override
public String getAbstractMethodName() {
return "goog.abstractMethod";
}
@Override
public String getSingletonGetterClassName(Node callNode) {
Node callArg = callNode.getFirstChild();
String callName = callArg.getQualifiedName();
// Use both the original name and the post-CollapseProperties name.
if (!("goog.addSingletonGetter".equals(callName) ||
"goog$addSingletonGetter".equals(callName)) ||
callNode.getChildCount()
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> != 2) {
return null;
}
return callArg.getNext().getQualifiedName();
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
functionType.defineDeclaredProperty("getInstance", getterType, false,
functionType.getSource());
functionType.defineDeclaredProperty("instance_", objectType, false,
functionType.getSource());
}
@Override
public String getGlobalObject() {
return "goog.global";
}
private final Set<String> propertyTestFunctions = ImmutableSet.of(
"goog.isDef", "goog.isNull", "goog.isDefAndNotNull",
"goog.isString", "goog.isNumber", "goog.isBoolean",
"goog.isFunction", "goog.isArray", "goog.isObject");
@Override
public boolean isPropertyTestFunction(Node call) {
Preconditions.checkArgument(call.getType() == Token.CALL);
return propertyTestFunctions.contains(
call.getFirstChild().getQualifiedName());
}
@Override
public ObjectLiteralCast getObjectLiteralCast(NodeTraversal t,
Node callNode) {
Preconditions.checkArgument(callNode.getType() == Token.CALL);
Node callName = callNode.getFirstChild();
if (!"goog.reflect.object".equals(callName.getQualifiedName()) ||
callName.getChildCount() != 2) {
return null;
}
Node typeNode = callName.getNext();
if (!typeNode.isQualifiedName()) {
return null;
}
Node objectNode = typeNode.getNext();
if (objectNode.getType() != Token.OBJECTLIT) {
t.getCompiler().report(JSError.make(t.getSourceName(), callNode,
OBJECTLIT_EXPECTED));
return null;
}
return new ObjectLiteralCast(typeNode.getQualifiedName(),
typeNode.getNext());
}
@Override
public boolean isOptionalParameter(Node parameter) {
return false;
}
@Override
public boolean isVarArgsParameter(Node parameter) {
return false;
}
@Override
public boolean isPrivate(String name) {
return false;
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return ImmutableList.<AssertionFunctionSpec>of(
new AssertionFunctionSpec("goog.asserts.assert"),
new AssertionFunctionSpec("goog.asserts.assertNumber",
JSTypeNative.NUMBER_TYPE),
new AssertionFunctionSpec("goog.asserts.assertString",
JSTypeNative.STRING_TYPE),
new AssertionFunctionSpec("goog.asserts.assertFunction",
JSTypeNative.FUNCTION_INSTANCE_TYPE),
new AssertionFunctionSpec("goog.asserts.assertObject",
JSTypeNative.OBJECT_TYPE),
new AssertionFunctionSpec("goog.asserts.assertArray",
JSTypeNative.ARRAY_TYPE),
// TODO(agrieve): It would be better if this could make the first
// parameter the type of the second parameter.
new AssertionFunctionSpec("goog.asserts.assertInstanceof",
JSTypeNative.OBJECT_TYPE)
);
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> case TYPE_PROP: return "type";
case SPECIAL_PROP_PROP: return "special_prop";
case LABEL_PROP: return "label";
case FINALLY_PROP: return "finally";
case LOCALCOUNT_PROP: return "localcount";
case TARGETBLOCK_PROP: return "targetblock";
case VARIABLE_PROP: return "variable";
case LASTUSE_PROP: return "lastuse";
case ISNUMBER_PROP: return "isnumber";
case DIRECTCALL_PROP: return "directcall";
case SPECIALCALL_PROP: return "specialcall";
case DEBUGSOURCE_PROP: return "debugsource";
case JSDOC_INFO_PROP: return "jsdoc_info";
case SKIP_INDEXES_PROP: return "skip_indexes";
case INCRDECR_PROP: return "incrdecr";
case MEMBER_TYPE_PROP: return "member_type";
case NAME_PROP: return "name";
case PARENTHESIZED_PROP: return "parenthesized";
case QUOTED_PROP: return "quoted";
case OPT_ARG_NAME: return "opt_arg";
case SYNTHETIC_BLOCK_PROP: return "synthetic";
case EMPTY_BLOCK: return "empty_block";
case ORIGINALNAME_PROP: return "originalname";
case SIDE_EFFECT_FLAGS: return "side_effect_flags";
case IS_CONSTANT_NAME: return "is_constant_name";
case IS_OPTIONAL_PARAM: return "is_optional_param";
case IS_VAR_ARGS_PARAM: return "is_var_args_param";
case IS_NAMESPACE: return "is_namespace";
case IS_DISPATCHER: return "is_dispatcher";
case DIRECTIVES: return "directives";
case DIRECT_EVAL: return "direct_eval";
case FREE_CALL: return "free_call";
default:
Kit.codeBug();
}
return null;
}
private static class NumberNode extends Node {
private static final long serialVersionUID = 1L;
NumberNode(double number) {
super(Token.NUMBER);
this.number = number;
}
public NumberNode(double number, int lineno, int charno) {
super(Token.NUMBER, lineno, charno);
this.number = number;
}
@Override
public double getDouble() {
return this.number;
}
@Override
public void setDouble(double d) {
this.number = d;
}
@Override
boolean isEquivalentTo(Node node, boolean compareJsType, boolean recurse) {
return (super.isEquivalentTo(node, compareJsType, recurse)
&& getDouble() == ((NumberNode) node).getDouble());
}
private double number;
}
private static class StringNode extends Node {
private static final long serialVersionUID = 1L;
StringNode(int type, String str) {
super(type);
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>.str = str;
}
StringNode(int type, String str, int lineno, int charno) {
super(type, lineno, charno);
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
/**
* returns the string content.
* @return non null.
*/
@Override
public String getString() {
return this.str;
}
/**
* sets the string content.
* @param str the new value. Non null.
*/
@Override
public void setString(String str) {
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
@Override
boolean isEquivalentTo(Node node, boolean compareJsType, boolean recurse) {
return (super.isEquivalentTo(node, compareJsType, recurse)
&& this.str.equals(((StringNode) node).str));
}
/**
* If the property is not defined, this was not a quoted key. The
* QUOTED_PROP int property is only assigned to STRING tokens used as
* object lit keys.
* @return true if this was a quoted string key in an object literal.
*/
@Override
public boolean isQuotedString() {
return getBooleanProp(QUOTED_PROP);
}
/**
* This should only be called for STRING nodes created in object lits.
*/
@Override
public void setQuotedString() {
putBooleanProp(QUOTED_PROP, true);
}
private String str;
}
// PropListItems are immutable so that they can be shared.
private static class PropListItem implements Serializable {
private static final long serialVersionUID = 1L;
final PropListItem next;
final int type;
final int intValue;
final Object objectValue;
PropListItem(int type, int intValue, PropListItem next) {
this(type, intValue, null, next);
}
PropListItem(int type, Object objectValue, PropListItem next) {
this(type, 0, objectValue, next);
}
PropListItem(
int type, int intValue, Object objectValue, PropListItem next) {
this.type = type;
this.intValue = intValue;
this.objectValue = objectValue;
this.next = next;
}
}
public Node(int nodeType) {
type = nodeType;
parent = null;
sourcePosition = -1;
}
public Node(int nodeType, Node child) {
Preconditions.checkArgument(child.parent == null,
"new child has existing parent");
Preconditions.checkArgument(child.next == null,
"new child has existing sibling");
type = nodeType;
parent = null;
first = last = child;
child.next = null;
child.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node right) {
Preconditions.checkArgument(left.parent == null,
"first new child has existing parent");
Preconditions.checkArgument(left.next == null,
"
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>first new child has existing sibling");
Preconditions.checkArgument(right.parent == null,
"second new child has existing parent");
Preconditions.checkArgument(right.next == null,
"second new child has existing sibling");
type = nodeType;
parent = null;
first = left;
last = right;
left.next = right;
left.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node mid, Node right) {
Preconditions.checkArgument(left.parent == null);
Preconditions.checkArgument(left.next == null);
Preconditions.checkArgument(mid.parent == null);
Preconditions.checkArgument(mid.next == null);
Preconditions.checkArgument(right.parent == null);
Preconditions.checkArgument(right.next == null);
type = nodeType;
parent = null;
first = left;
last = right;
left.next = mid;
left.parent = this;
mid.next = right;
mid.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right) {
Preconditions.checkArgument(left.parent == null);
Preconditions.checkArgument(left.next == null);
Preconditions.checkArgument(mid.parent == null);
Preconditions.checkArgument(mid.next == null);
Preconditions.checkArgument(mid2.parent == null);
Preconditions.checkArgument(mid2.next == null);
Preconditions.checkArgument(right.parent == null);
Preconditions.checkArgument(right.next == null);
type = nodeType;
parent = null;
first = left;
last = right;
left.next = mid;
left.parent = this;
mid.next = mid2;
mid.parent = this;
mid2.next = right;
mid2.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, int lineno, int charno) {
type = nodeType;
parent = null;
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node child, int lineno, int charno) {
this(nodeType, child);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node right, int lineno, int charno) {
this(nodeType, left, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node right,
int lineno, int charno) {
this(nodeType, left, mid, right);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node left, Node mid, Node mid2, Node right,
int lineno, int charno) {
this(nodeType, left, mid, mid2, right);
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children, int lineno, int charno) {
this(nodeType, children);
sourcePosition = mergeLineCharNo(lineno, charno);
}
public Node(int nodeType, Node[] children) {
this.type = nodeType;
parent = null;
if (children.length != 0) {
this.first = children[0];
this.last = children[children.length - 1];
for (int i = 1; i < children.length; i++) {
if (null != children[i - 1].next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
children[i - 1].next = children[i];
Preconditions.checkArgument(children[i - 1].parent == null);
children[i - 1].parent = this;
}
Preconditions.checkArgument(children[children.length - 1].parent == null);
children[children.length - 1].parent = this;
if (null != this.last.next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
}
}
public static Node newNumber(double number) {
return new NumberNode(number);
}
public static Node newNumber(double number, int lineno, int charno) {
return new NumberNode(number, lineno, charno);
}
public static Node newString(String str) {
return new StringNode(Token.STRING, str);
}
public static Node newString(int type, String str) {
return new StringNode(type, str);
}
public static Node newString(String str, int lineno, int charno) {
return new StringNode(Token.STRING, str, lineno, charno);
}
public static Node newString(int type, String str, int lineno, int charno) {
return new StringNode(type, str, lineno, charno);
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public boolean hasChildren() {
return first != null;
}
public Node getFirstChild() {
return first;
}
public Node getLastChild() {
return last;
}
public Node getNext() {
return next;
}
public Node getChildBefore(Node child) {
if (child == first) {
return null;
}
Node n = first;
while (n.next != child) {
n = n.next;
if (n == null) {
throw new RuntimeException("node is not a child");
}
}
return n;
}
public Node getChildAtIndex(int i) {
Node n = first;
while (i > 0) {
n = n.next;
i--;
}
return n;
}
public Node getLastSibling() {
Node n = this;
while (n.next != null) {
n = n.next;
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> }
return n;
}
public void addChildToFront(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
child.next = first;
first = child;
if (last == null) {
last = child;
}
}
public void addChildToBack(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
child.next = null;
if (last == null) {
first = last = child;
return;
}
last.next = child;
last = child;
}
public void addChildrenToFront(Node children) {
for (Node child = children; child != null; child = child.next) {
Preconditions.checkArgument(child.parent == null);
child.parent = this;
}
Node lastSib = children.getLastSibling();
lastSib.next = first;
first = children;
if (last == null) {
last = lastSib;
}
}
public void addChildrenToBack(Node children) {
for (Node child = children; child != null; child = child.next) {
Preconditions.checkArgument(child.parent == null);
child.parent = this;
}
if (last != null) {
last.next = children;
}
last = children.getLastSibling();
if (first == null) {
first = children;
}
}
/**
* Add 'child' before 'node'.
*/
public void addChildBefore(Node newChild, Node node) {
Preconditions.checkArgument(node != null,
"The existing child node of the parent should not be null.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
if (first == node) {
newChild.parent = this;
newChild.next = first;
first = newChild;
return;
}
Node prev = getChildBefore(node);
addChildAfter(newChild, prev);
}
/**
* Add 'child' after 'node'.
*/
public void addChildAfter(Node newChild, Node node) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
newChild.parent = this;
newChild.next = node.next;
node.next = newChild;
if (last == node) {
last = newChild;
}
}
/**
* Detach a child from its parent and siblings.
*/
public void removeChild(Node child) {
Node prev = getChildBefore(child);
if (prev == null)
first = first.next;
else
prev.next = child.next;
if (child == last) last = prev;
child.next =
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> null;
child.parent = null;
}
/**
* Detaches child from Node and replaces it with newChild.
*/
public void replaceChild(Node child, Node newChild) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(child);
newChild.next = child.next;
newChild.parent = this;
if (child == first) {
first = newChild;
} else {
Node prev = getChildBefore(child);
prev.next = newChild;
}
if (child == last)
last = newChild;
child.next = null;
child.parent = null;
}
public void replaceChildAfter(Node prevChild, Node newChild) {
Preconditions.checkArgument(prevChild.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(prevChild);
Node child = prevChild.next;
newChild.next = child.next;
newChild.parent = this;
prevChild.next = newChild;
if (child == last)
last = newChild;
child.next = null;
child.parent = null;
}
@VisibleForTesting
PropListItem lookupProperty(int propType) {
PropListItem x = propListHead;
while (x != null && propType != x.type) {
x = x.next;
}
return x;
}
/**
* Clone the properties from the provided node without copying
* the property object. The recieving node may not have any
* existing properties.
* @param other The node to clone properties from.
* @return this node.
*/
public Node clonePropsFrom(Node other) {
Preconditions.checkState(this.propListHead == null,
"Node has existing properties.");
this.propListHead = other.propListHead;
return this;
}
public void removeProp(int propType) {
PropListItem result = removeProp(propListHead, propType);
if (result != propListHead) {
propListHead = result;
}
}
/**
* @param item The item to inspect
* @param propType The property to look for
* @return The replacement list if the property was removed, or
* 'item' otherwise.
*/
private PropListItem removeProp(PropListItem item, int propType) {
if (item == null) {
return null;
} else if (item.type == propType) {
return item.next;
} else {
PropListItem result = removeProp(item.next, propType);
if (result != item.next) {
return new PropListItem(
item.type, item.intValue, item.
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>objectValue, result);
} else {
return item;
}
}
}
public Object getProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
return null;
}
return item.objectValue;
}
public boolean getBooleanProp(int propType) {
return getIntProp(propType) != 0;
}
/**
* Returns the integer value for the property, or 0 if the property
* is not defined.
*/
public int getIntProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
return 0;
}
return item.intValue;
}
public int getExistingIntProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
Kit.codeBug();
}
return item.intValue;
}
public void putProp(int propType, Object value) {
removeProp(propType);
if (value != null) {
propListHead = new PropListItem(propType, value, propListHead);
}
}
public void putBooleanProp(int propType, boolean value) {
putIntProp(propType, value ? 1 : 0);
}
public void putIntProp(int propType, int value) {
removeProp(propType);
if (value != 0) {
propListHead = new PropListItem(propType, value, propListHead);
}
}
// Gets all the property types, in sorted order.
private int[] getSortedPropTypes() {
int count = 0;
for (PropListItem x = propListHead; x != null; x = x.next) {
count++;
}
int[] keys = new int[count];
for (PropListItem x = propListHead; x != null; x = x.next) {
count--;
keys[count] = x.type;
}
Arrays.sort(keys);
return keys;
}
public int getLineno() {
return extractLineno(sourcePosition);
}
public int getCharno() {
return extractCharno(sourcePosition);
}
public int getSourcePosition() {
return sourcePosition;
}
/** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */
public double getDouble() throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a number node");
}
}
/** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */
public void setDouble(double s) throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public String getString() throws UnsupportedOperationException {
if (this
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public void setString(String s) throws UnsupportedOperationException {
if (this.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
@Override
public String toString() {
return toString(true, true, true);
}
public String toString(
boolean printSource,
boolean printAnnotations,
boolean printType) {
if (Token.printTrees) {
StringBuilder sb = new StringBuilder();
toString(sb, printSource, printAnnotations, printType);
return sb.toString();
}
return String.valueOf(type);
}
private void toString(
StringBuilder sb,
boolean printSource,
boolean printAnnotations,
boolean printType) {
if (Token.printTrees) {
sb.append(Token.name(type));
if (this instanceof StringNode) {
sb.append(' ');
sb.append(getString());
} else if (type == Token.FUNCTION) {
sb.append(' ');
// In the case of JsDoc trees, the first child is often not a string
// which causes exceptions to be thrown when calling toString or
// toStringTree.
if (first == null || first.getType() != Token.NAME) {
sb.append("<invalid>");
} else {
sb.append(first.getString());
}
} else if (this instanceof ScriptOrFnNode) {
ScriptOrFnNode sof = (ScriptOrFnNode) this;
if (this instanceof FunctionNode) {
FunctionNode fn = (FunctionNode) this;
sb.append(' ');
sb.append(fn.getFunctionName());
}
if (printSource) {
sb.append(" [source name: ");
sb.append(sof.getSourceName());
sb.append("] [encoded source length: ");
sb.append(sof.getEncodedSourceEnd() - sof.getEncodedSourceStart());
sb.append("] [base line: ");
sb.append(sof.getBaseLineno());
sb.append("] [end line: ");
sb.append(sof.getEndLineno());
sb.append(']');
}
} else if (type == Token.NUMBER) {
sb.append(' ');
sb.append(getDouble());
}
if (printSource) {
int lineno = getLineno();
if (lineno != -1) {
sb.append(' ');
sb.append(lineno);
}
}
if (printAnnotations) {
int[] keys = getSortedPropTypes();
for (int i = 0; i < keys.length; i++) {
int type = keys[i];
PropListItem x = lookupProperty(type);
sb.append(" [");
sb.append(propToString(type));
sb.append(": ");
String value;
switch (type) {
case
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> TARGETBLOCK_PROP: // can't add this as it recurses
value = "target block property";
break;
case LOCAL_BLOCK_PROP: // can't add this as it is dull
value = "last local block";
break;
case ISNUMBER_PROP:
switch (x.intValue) {
case BOTH:
value = "both";
break;
case RIGHT:
value = "right";
break;
case LEFT:
value = "left";
break;
default:
throw Kit.codeBug();
}
break;
case SPECIALCALL_PROP:
switch (x.intValue) {
case SPECIALCALL_EVAL:
value = "eval";
break;
case SPECIALCALL_WITH:
value = "with";
break;
default:
// NON_SPECIALCALL should not be stored
throw Kit.codeBug();
}
break;
default:
Object obj = x.objectValue;
if (obj != null) {
value = obj.toString();
} else {
value = String.valueOf(x.intValue);
}
break;
}
sb.append(value);
sb.append(']');
}
}
if (printType) {
if (jsType != null) {
String jsTypeString = jsType.toString();
if (jsTypeString != null) {
sb.append(" : ");
sb.append(jsTypeString);
}
}
}
}
}
public String toStringTree() {
return toStringTreeImpl();
}
private String toStringTreeImpl() {
try {
StringBuilder s = new StringBuilder();
appendStringTree(s);
return s.toString();
} catch (IOException e) {
throw new RuntimeException("Should not happen\n" + e);
}
}
public void appendStringTree(Appendable appendable) throws IOException {
toStringTreeHelper(this, 0, appendable);
}
private static void toStringTreeHelper(Node n, int level, Appendable sb)
throws IOException {
if (Token.printTrees) {
for (int i = 0; i != level; ++i) {
sb.append(" ");
}
sb.append(n.toString());
sb.append('\n');
for (Node cursor = n.getFirstChild();
cursor != null;
cursor = cursor.getNext()) {
toStringTreeHelper(cursor, level + 1, sb);
}
}
}
int type; // type of the node; Token.NAME for example
Node next; // next sibling
private Node first; // first element of a linked list of children
private Node last; // last element of a linked list of children
/**
* Linked list of properties. Since vast majority of nodes would have
* no more then 2 properties, linked list saves memory and provides
* fast lookup. If this does not holds, propListHead can be replaced
* by UintMap.
*/
private PropListItem propListHead;
/**
* COLUMN_BITS represents how many of the lower-order bits of
* sourcePosition are reserved for storing the column number.
* Bits above these store the line number.
* This gives us decent position information for
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> everything except
* files already passed through a minimizer, where lines might
* be longer than 4096 characters.
*/
public static final int COLUMN_BITS = 12;
/**
* MAX_COLUMN_NUMBER represents the maximum column number that can
* be represented. JSCompiler's modifications to Rhino cause all
* tokens located beyond the maximum column to MAX_COLUMN_NUMBER.
*/
public static final int MAX_COLUMN_NUMBER = (1 << COLUMN_BITS) - 1;
/**
* COLUMN_MASK stores a value where bits storing the column number
* are set, and bits storing the line are not set. It's handy for
* separating column number from line number.
*/
public static final int COLUMN_MASK = MAX_COLUMN_NUMBER;
/**
* Source position of this node. The position is encoded with the
* column number in the low 12 bits of the integer, and the line
* number in the rest. Create some handy constants so we can change this
* size if we want.
*/
private int sourcePosition;
private JSType jsType;
private Node parent;
//==========================================================================
// Source position management
public void setLineno(int lineno) {
int charno = getCharno();
if (charno == -1) {
charno = 0;
}
sourcePosition = mergeLineCharNo(lineno, charno);
}
public void setCharno(int charno) {
sourcePosition = mergeLineCharNo(getLineno(), charno);
}
public void setSourcePositionForTree(int sourcePosition) {
this.sourcePosition = sourcePosition;
for (Node child = getFirstChild();
child != null; child = child.getNext()) {
child.setSourcePositionForTree(sourcePosition);
}
}
/**
* Merges the line number and character number in one integer. The Character
* number takes the first 12 bits and the line number takes the rest. If
* the character number is greater than <code>2<sup>12</sup>-1</code> it is
* adjusted to <code>2<sup>12</sup>-1</code>.
*/
protected static int mergeLineCharNo(int lineno, int charno) {
if (lineno < 0 || charno < 0) {
return -1;
} else if ((charno & ~COLUMN_MASK) != 0) {
return lineno << COLUMN_BITS | COLUMN_MASK;
} else {
return lineno << COLUMN_BITS | (charno & COLUMN_MASK);
}
}
/**
* Extracts the line number and character number from a merged line char
* number (see {@link #mergeLineCharNo(int, int)}).
*/
protected static int extractLineno(int lineCharNo) {
if (lineCharNo == -1) {
return -1;
} else {
return lineCharNo >>> COLUMN_BITS;
}
}
/**
* Extracts the character number and character number from a merged line
* char number (see {@link #mergeLineCharNo(int, int)}).
*/
protected
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>());
}
/**
* Iterator to go up the ancestor tree.
*/
public static class AncestorIterable implements Iterable<Node> {
private Node cur;
/**
* @param cur The node to start.
*/
AncestorIterable(Node cur) {
this.cur = cur;
}
public Iterator<Node> iterator() {
return new Iterator<Node>() {
public boolean hasNext() {
return cur != null;
}
public Node next() {
if (!hasNext()) throw new NoSuchElementException();
Node n = cur;
cur = cur.getParent();
return n;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* Check for one child more efficiently than by iterating over all the
* children as is done with Node.getChildCount().
*
* @return Whether the node has exactly one child.
*/
public boolean hasOneChild() {
return first != null && first == last;
}
/**
* Check for more than one child more efficiently than by iterating over all
* the children as is done with Node.getChildCount().
*
* @return Whether the node more than one child.
*/
public boolean hasMoreThanOneChild() {
return first != null && first != last;
}
public int getChildCount() {
int c = 0;
for (Node n = first; n != null; n = n.next)
c++;
return c;
}
// Intended for testing and verification only.
public boolean hasChild(Node child) {
for (Node n = first; n != null; n = n.getNext()) {
if (child == n) {
return true;
}
}
return false;
}
/**
* Checks if the subtree under this node is the same as another subtree.
* Returns null if it's equal, or a message describing the differences.
*/
public String checkTreeEquals(Node node2) {
NodeMismatch diff = checkTreeEqualsImpl(node2);
if (diff != null) {
return "Node tree inequality:" +
"\nTree1:\n" + toStringTree() +
"\n\nTree2:\n" + node2.toStringTree() +
"\n\nSubtree1: " + diff.nodeA.toStringTree() +
"\n\nSubtree2: " + diff.nodeB.toStringTree();
}
return null;
}
/**
* Helper function to ignore differences in Node subclasses that are no longer
* used.
*/
@SuppressWarnings("unchecked")
static private Class getNodeClass(Node n) {
Class c = n.getClass();
if (c == FunctionNode.class || c == ScriptOrFnNode.class) {
return Node.class;
}
return c;
}
/**
* Compare this node to node2 recursively and return the first pair of nodes
* that differs doing a preorder depth-first traversal. Package private for
* testing. Returns null if the nodes are equivalent.
*/
NodeMismatch checkTreeEqualsImpl(Node node2) {
if (!isEquivalentTo(node2, false, false)) {
return new NodeMismatch
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>(this, node2);
}
NodeMismatch res = null;
Node n, n2;
for (n = first, n2 = node2.first;
res == null && n != null;
n = n.next, n2 = n2.next) {
if (node2 == null) {
throw new IllegalStateException();
}
res = n.checkTreeEqualsImpl(n2);
if (res != null) {
return res;
}
}
return res;
}
/**
* Compare this node to node2 recursively and return the first pair of nodes
* that differs doing a preorder depth-first traversal. Package private for
* testing. Returns null if the nodes are equivalent.
*/
NodeMismatch checkTreeTypeAwareEqualsImpl(Node node2) {
// Do a non-recursive equivalents check.
if (!isEquivalentTo(node2, true, false)) {
return new NodeMismatch(this, node2);
}
NodeMismatch res = null;
Node n, n2;
for (n = first, n2 = node2.first;
res == null && n != null;
n = n.next, n2 = n2.next) {
res = n.checkTreeTypeAwareEqualsImpl(n2);
if (res != null) {
return res;
}
}
return res;
}
public static String tokenToName(int token) {
switch (token) {
case Token.ERROR: return "error";
case Token.EOF: return "eof";
case Token.EOL: return "eol";
case Token.ENTERWITH: return "enterwith";
case Token.LEAVEWITH: return "leavewith";
case Token.RETURN: return "return";
case Token.GOTO: return "goto";
case Token.IFEQ: return "ifeq";
case Token.IFNE: return "ifne";
case Token.SETNAME: return "setname";
case Token.BITOR: return "bitor";
case Token.BITXOR: return "bitxor";
case Token.BITAND: return "bitand";
case Token.EQ: return "eq";
case Token.NE: return "ne";
case Token.LT: return "lt";
case Token.LE: return "le";
case Token.GT: return "gt";
case Token.GE: return "ge";
case Token.LSH: return "lsh";
case Token.RSH: return "rsh";
case Token.URSH: return "ursh";
case Token.ADD: return "add";
case Token.SUB: return "sub";
case Token.MUL: return "mul";
case Token.DIV: return "div";
case Token.MOD: return "mod";
case Token.BITNOT: return "bitnot";
case Token.NEG: return "neg";
case Token.NEW: return "new";
case Token.DELPROP: return "delprop";
case Token.TYPEOF: return "typeof";
case Token.
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>GETPROP: return "getprop";
case Token.SETPROP: return "setprop";
case Token.GETELEM: return "getelem";
case Token.SETELEM: return "setelem";
case Token.CALL: return "call";
case Token.NAME: return "name";
case Token.NUMBER: return "number";
case Token.STRING: return "string";
case Token.NULL: return "null";
case Token.THIS: return "this";
case Token.FALSE: return "false";
case Token.TRUE: return "true";
case Token.SHEQ: return "sheq";
case Token.SHNE: return "shne";
case Token.REGEXP: return "regexp";
case Token.POS: return "pos";
case Token.BINDNAME: return "bindname";
case Token.THROW: return "throw";
case Token.IN: return "in";
case Token.INSTANCEOF: return "instanceof";
case Token.GETVAR: return "getvar";
case Token.SETVAR: return "setvar";
case Token.TRY: return "try";
case Token.TYPEOFNAME: return "typeofname";
case Token.THISFN: return "thisfn";
case Token.SEMI: return "semi";
case Token.LB: return "lb";
case Token.RB: return "rb";
case Token.LC: return "lc";
case Token.RC: return "rc";
case Token.LP: return "lp";
case Token.RP: return "rp";
case Token.COMMA: return "comma";
case Token.ASSIGN: return "assign";
case Token.ASSIGN_BITOR: return "assign_bitor";
case Token.ASSIGN_BITXOR: return "assign_bitxor";
case Token.ASSIGN_BITAND: return "assign_bitand";
case Token.ASSIGN_LSH: return "assign_lsh";
case Token.ASSIGN_RSH: return "assign_rsh";
case Token.ASSIGN_URSH: return "assign_ursh";
case Token.ASSIGN_ADD: return "assign_add";
case Token.ASSIGN_SUB: return "assign_sub";
case Token.ASSIGN_MUL: return "assign_mul";
case Token.ASSIGN_DIV: return "assign_div";
case Token.ASSIGN_MOD: return "assign_mod";
case Token.HOOK: return "hook";
case Token.COLON: return "colon";
case Token.OR: return "or";
case Token.AND: return "and";
case Token.INC: return "inc";
case Token.DEC: return "dec";
case Token.DOT: return "dot";
case Token.FUNCTION: return "function";
case Token.EXPORT: return "export";
case Token.IMPORT: return "import";
case Token.IF: return "if";
case Token.ELSE: return "else";
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
case Token.SWITCH: return "switch";
case Token.CASE: return "case";
case Token.DEFAULT: return "default";
case Token.WHILE: return "while";
case Token.DO: return "do";
case Token.FOR: return "for";
case Token.BREAK: return "break";
case Token.CONTINUE: return "continue";
case Token.VAR: return "var";
case Token.WITH: return "with";
case Token.CATCH: return "catch";
case Token.FINALLY: return "finally";
case Token.RESERVED: return "reserved";
case Token.NOT: return "not";
case Token.VOID: return "void";
case Token.BLOCK: return "block";
case Token.ARRAYLIT: return "arraylit";
case Token.OBJECTLIT: return "objectlit";
case Token.LABEL: return "label";
case Token.TARGET: return "target";
case Token.LOOP: return "loop";
case Token.EXPR_VOID: return "expr_void";
case Token.EXPR_RESULT: return "expr_result";
case Token.JSR: return "jsr";
case Token.SCRIPT: return "script";
case Token.EMPTY: return "empty";
case Token.GET_REF: return "get_ref";
case Token.REF_SPECIAL: return "ref_special";
}
return "<unknown="+token+">";
}
/** Returns true if this node is equivalent semantically to another */
public boolean isEquivalentTo(Node node) {
return isEquivalentTo(node, false, true);
}
/**
* Returns true if this node is equivalent semantically to another and
* the types are equivalent.
*/
public boolean isEquivalentToTyped(Node node) {
return isEquivalentTo(node, true, true);
}
/**
* @param compareJsType Whether to compare the JSTypes of the nodes.
* @param recurse Whether to compare the children of the current node, if
* not only the the count of the children are compared.
* @return Whether this node is equivalent semantically to the provided node.
*/
boolean isEquivalentTo(Node node, boolean compareJsType, boolean recurse) {
if (type != node.getType()
|| getChildCount() != node.getChildCount()
|| getNodeClass(this) != getNodeClass(node)) {
return false;
}
if (compareJsType && !JSType.isEquivalent(jsType, node.getJSType())) {
return false;
}
if (type == Token.ARRAYLIT) {
try {
int[] indices1 = (int[]) getProp(Node.SKIP_INDEXES_PROP);
int[] indices2 = (int[]) node.getProp(Node.SKIP_INDEXES_PROP);
if (indices1 == null) {
if (indices2 != null) {
return false;
}
} else if (indices2 == null) {
return false;
} else if (indices1.length != indices2.length) {
return false
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>;
} else {
for (int i = 0; i < indices1.length; i++) {
if (indices1[i] != indices2[i]) {
return false;
}
}
}
} catch (Exception e) {
return false;
}
} else if (type == Token.INC || type == Token.DEC) {
int post1 = this.getIntProp(INCRDECR_PROP);
int post2 = node.getIntProp(INCRDECR_PROP);
if (post1 != post2) {
return false;
}
} else if (type == Token.STRING) {
int quoted1 = this.getIntProp(QUOTED_PROP);
int quoted2 = node.getIntProp(QUOTED_PROP);
if (quoted1 != quoted2) {
return false;
}
}
if (recurse) {
Node n, n2;
for (n = first, n2 = node.first;
n != null;
n = n.next, n2 = n2.next) {
if (!n.isEquivalentTo(n2, compareJsType, true)) {
return false;
}
}
}
return true;
}
public boolean hasSideEffects() {
switch (type) {
case Token.EXPR_VOID:
case Token.COMMA:
if (last != null)
return last.hasSideEffects();
else
return true;
case Token.HOOK:
if (first == null || first.next == null || first.next.next == null) {
Kit.codeBug();
}
return first.next.hasSideEffects() && first.next.next.hasSideEffects();
case Token.ERROR: // Avoid cascaded error messages
case Token.EXPR_RESULT:
case Token.ASSIGN:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ENTERWITH:
case Token.LEAVEWITH:
case Token.RETURN:
case Token.GOTO:
case Token.IFEQ:
case Token.IFNE:
case Token.NEW:
case Token.DELPROP:
case Token.SETNAME:
case Token.SETPROP:
case Token.SETELEM:
case Token.CALL:
case Token.THROW:
case Token.RETHROW:
case Token.SETVAR:
case Token.CATCH_SCOPE:
case Token.RETURN_RESULT:
case Token.SET_REF:
case Token.DEL_REF:
case Token.REF_CALL:
case Token.TRY:
case Token.SEMI:
case Token.INC:
case Token.DEC:
case Token.EXPORT:
case Token.IMPORT:
case Token.IF:
case Token.ELSE:
case Token.SWITCH:
case Token.WHILE:
case Token
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>.DO:
case Token.FOR:
case Token.BREAK:
case Token.CONTINUE:
case Token.VAR:
case Token.CONST:
case Token.WITH:
case Token.CATCH:
case Token.FINALLY:
case Token.BLOCK:
case Token.LABEL:
case Token.TARGET:
case Token.LOOP:
case Token.JSR:
case Token.SETPROP_OP:
case Token.SETELEM_OP:
case Token.LOCAL_BLOCK:
case Token.SET_REF_OP:
return true;
default:
return false;
}
}
/**
* This function takes a set of GETPROP nodes and produces a string that is
* each property separated by dots. If the node ultimately under the left
* sub-tree is not a simple name, this is not a valid qualified name.
*
* @return a null if this is not a qualified name, or a dot-separated string
* of the name and properties.
*/
public String getQualifiedName() {
if (type == Token.NAME) {
return getString();
} else if (type == Token.GETPROP) {
String left = getFirstChild().getQualifiedName();
if (left == null) {
return null;
}
return left + "." + getLastChild().getString();
} else if (type == Token.THIS) {
return "this";
} else {
return null;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name, such as
* <code>x</code> or <code>a.b.c</code> or <code>this.a</code>.
*/
public boolean isQualifiedName() {
switch (getType()) {
case Token.NAME:
case Token.THIS:
return true;
case Token.GETPROP:
return getFirstChild().isQualifiedName();
default:
return false;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name without
* a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code>
* .
*/
public boolean isUnscopedQualifiedName() {
switch (getType()) {
case Token.NAME:
return true;
case Token.GETPROP:
return getFirstChild().isUnscopedQualifiedName();
default:
return false;
}
}
// ==========================================================================
// Mutators
/**
* Removes this node from its parent. Equivalent to:
* node.getParent().removeChild();
*/
public Node detachFromParent() {
Preconditions.checkState(parent != null);
parent.removeChild(this);
return this;
}
/**
* Removes the first child of Node. Equivalent to:
* node.removeChild(node.getFirstChild());
*
* @return The removed Node.
*/
public Node removeFirstChild() {
Node child = first;
if (child != null) {
removeChild(child);
}
return child;
}
/**
* @return A Node that is the head of the list of children.
*/
public Node removeChildren() {
Node children = first;
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> for (Node child = first; child != null; child = child.getNext()) {
child.parent = null;
}
first = null;
last = null;
return children;
}
/**
* Removes all children from this node and isolates the children from each
* other.
*/
public void detachChildren() {
for (Node child = first; child != null;) {
Node nextChild = child.getNext();
child.parent = null;
child.next = null;
child = nextChild;
}
first = null;
last = null;
}
public Node removeChildAfter(Node prev) {
Preconditions.checkArgument(prev.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(prev.next != null,
"no next sibling.");
Node child = prev.next;
prev.next = child.next;
if (child == last) last = prev;
child.next = null;
child.parent = null;
return child;
}
/**
* @return A detached clone of the Node, specifically excluding its children.
*/
public Node cloneNode() {
Node result;
try {
result = (Node) super.clone();
// PropListItem lists are immutable and can be shared so there is no
// need to clone them here.
result.next = null;
result.first = null;
result.last = null;
result.parent = null;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e.getMessage());
}
return result;
}
/**
* @return A detached clone of the Node and all its children.
*/
public Node cloneTree() {
Node result = cloneNode();
for (Node n2 = getFirstChild(); n2 != null; n2 = n2.getNext()) {
Node n2clone = n2.cloneTree();
n2clone.parent = result;
if (result.last != null) {
result.last.next = n2clone;
}
if (result.first == null) {
result.first = n2clone;
}
result.last = n2clone;
}
return result;
}
/**
* Copies source file and name information from the other
* node given to the current node. Used for maintaining
* debug information across node append and remove operations.
* @return this
*/
public Node copyInformationFrom(Node other) {
if (getProp(ORIGINALNAME_PROP) == null) {
putProp(ORIGINALNAME_PROP, other.getProp(ORIGINALNAME_PROP));
}
if (getProp(SOURCENAME_PROP) == null) {
putProp(SOURCENAME_PROP, other.getProp(SOURCENAME_PROP));
sourcePosition = other.sourcePosition;
}
return this;
}
/**
* Copies source file and name information from the other node to the
* entire tree rooted at this node.
* @return this
*/
public Node copyInformationFromForTree(Node other) {
copyInformationFrom(other);
for (Node child = getFirstChild();
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
child != null; child = child.getNext()) {
child.copyInformationFromForTree(other);
}
return this;
}
//==========================================================================
// Custom annotations
public JSType getJSType() {
return jsType;
}
public void setJSType(JSType jsType) {
this.jsType = jsType;
}
public FileLevelJsDocBuilder getJsDocBuilderForNode() {
return new FileLevelJsDocBuilder();
}
/**
* An inner class that provides back-door access to the license
* property of the JSDocInfo property for this node. This is only
* meant to be used for top level script nodes where the
* {@link com.google.javascript.jscomp.parsing.JsDocInfoParser} needs to
* be able to append directly to the top level node, not just the
* current node.
*/
public class FileLevelJsDocBuilder {
public void append(String fileLevelComment) {
JSDocInfo jsDocInfo = getJSDocInfo();
if (jsDocInfo == null) {
// TODO(user): Is there a way to determine whether to
// parse the JsDoc documentation from here?
jsDocInfo = new JSDocInfo(false);
}
String license = jsDocInfo.getLicense();
if (license == null) {
license = "";
}
jsDocInfo.setLicense(license + fileLevelComment);
setJSDocInfo(jsDocInfo);
}
}
/**
* Get the {@link JSDocInfo} attached to this node.
* @return the information or {@code null} if no JSDoc is attached to this
* node
*/
public JSDocInfo getJSDocInfo() {
return (JSDocInfo) getProp(JSDOC_INFO_PROP);
}
/**
* Sets the {@link JSDocInfo} attached to this node.
*/
public void setJSDocInfo(JSDocInfo info) {
putProp(JSDOC_INFO_PROP, info);
}
/**
* Sets whether this node is a variable length argument node. This
* method is meaningful only on {@link Token#NAME} nodes
* used to define a {@link Token#FUNCTION}'s argument list.
*/
public void setVarArgs(boolean varArgs) {
putBooleanProp(VAR_ARGS_NAME, varArgs);
}
/**
* Returns whether this node is a variable length argument node. This
* method's return value is meaningful only on {@link Token#NAME} nodes
* used to define a {@link Token#FUNCTION}'s argument list.
*/
public boolean isVarArgs() {
return getBooleanProp(VAR_ARGS_NAME);
}
/**
* Sets whether this node is an optional argument node. This
* method is meaningful only on {@link Token#NAME} nodes
* used to define a {@link Token#FUNCTION}'s argument list.
*/
public void setOptionalArg(boolean optionalArg) {
putBooleanProp(OPT_ARG_NAME, optionalArg);
}
/**
* Returns whether this node is an optional argument node. This
* method's return value is meaningful
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
public void setSideEffectFlags(int flags) {
Preconditions.checkArgument(
getType() == Token.CALL || getType() == Token.NEW,
"setIsNoSideEffectsCall only supports CALL and NEW nodes, got " +
Token.name(getType()));
putIntProp(SIDE_EFFECT_FLAGS, flags);
}
public void setSideEffectFlags(SideEffectFlags flags) {
setSideEffectFlags(flags.valueOf());
}
/**
* Returns the side effects flags for this node.
*/
public int getSideEffectFlags() {
return getIntProp(SIDE_EFFECT_FLAGS);
}
/**
* A helper class for getting and setting the side-effect flags.
* @author johnlenz@google.com (John Lenz)
*/
public static class SideEffectFlags {
private int value = Node.SIDE_EFFECTS_ALL;
public SideEffectFlags() {
}
public SideEffectFlags(int value) {
this.value = value;
}
public int valueOf() {
return value;
}
/** All side-effect occur and the returned results are non-local. */
public void setAllFlags() {
value = Node.SIDE_EFFECTS_ALL;
}
/** No side-effects occur and the returned results are local. */
public void clearAllFlags() {
value = Node.NO_SIDE_EFFECTS | Node.FLAG_LOCAL_RESULTS;
}
public boolean areAllFlagsSet() {
return value == Node.SIDE_EFFECTS_ALL;
}
/**
* Preserve the return result flag, but clear the others:
* no global state change, no throws, no this change, no arguments change
*/
public void clearSideEffectFlags() {
value |= Node.NO_SIDE_EFFECTS;
}
public void setMutatesGlobalState() {
// Modify global means everything must be assumed to be modified.
removeFlag(Node.FLAG_GLOBAL_STATE_UNMODIFIED);
removeFlag(Node.FLAG_ARGUMENTS_UNMODIFIED);
removeFlag(Node.FLAG_THIS_UNMODIFIED);
}
public void setThrows() {
removeFlag(Node.FLAG_NO_THROWS);
}
public void setMutatesThis() {
removeFlag(Node.FLAG_THIS_UNMODIFIED);
}
public void setMutatesArguments() {
removeFlag(Node.FLAG_ARGUMENTS_UNMODIFIED);
}
public void setReturnsTainted() {
removeFlag(Node.FLAG_LOCAL_RESULTS);
}
private void removeFlag(int flag) {
value &= ~flag;
}
}
/**
* @return Whether the only side-effect is "modifies this"
*/
public boolean isOnlyModifiesThisCall() {
return areBitFlagsSet(
getSideEffectFlags() & Node.NO_SIDE_EFFECTS,
Node.FLAG_GLOBAL_STATE_UNMODIFIED
| Node.FLAG_ARGUMENTS_UNMODIFIED
| Node.FLAG_NO_THROWS);
}
/**
* Returns true if this node is a function or constructor call that
* has no side effects.
*/
public boolean
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.Arrays;
import java.util.List;
/**
* Tests {@link TypeCheck}.
*
*/
public class TypeCheckTest extends CompilerTypeTestCase {
private CheckLevel reportMissingOverrides = CheckLevel.WARNING;
@Override
public void setUp() throws Exception {
super.setUp();
reportMissingOverrides = CheckLevel.WARNING;
}
public void testInitialTypingScope() {
Scope s = new TypedScopeCreator(compiler,
new DefaultCodingConvention()).createInitialScope(
new Node(Token.BLOCK));
assertEquals(ARRAY_FUNCTION_TYPE, s.getVar("Array").getType());
assertEquals(BOOLEAN_OBJECT_FUNCTION_TYPE,
s.getVar("Boolean").getType());
assertEquals(DATE_FUNCTION_TYPE, s.getVar("Date").getType());
assertEquals(ERROR_FUNCTION_TYPE, s.getVar("Error").getType());
assertEquals(EVAL_ERROR_FUNCTION_TYPE,
s.getVar("EvalError").getType());
assertEquals(NUMBER_OBJECT_FUNCTION_TYPE,
s.getVar("Number").getType());
assertEquals(OBJECT_FUNCTION_TYPE, s.getVar("Object").getType());
assertEquals(RANGE_ERROR_FUNCTION_TYPE,
s.getVar("RangeError").getType());
assertEquals(REFERENCE_ERROR_FUNCTION_TYPE,
s.getVar("ReferenceError").getType());
assertEquals(REGEXP_FUNCTION_TYPE, s.getVar("RegExp").getType());
assertEquals(STRING_OBJECT_FUNCTION_TYPE,
s.getVar("String").getType());
assertEquals(SYNTAX_ERROR_FUNCTION_TYPE,
s.getVar("SyntaxError").getType());
assertEquals(TYPE_ERROR_FUNCTION_TYPE,
s.getVar("TypeError").getType());
assertEquals(URI_ERROR_FUNCTION_TYPE,
s.getVar("URIError").getType());
}
public void testTypeCheck1() throws Exception {
testTypes("/**@return {void}*/function foo
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>;",
"variable b redefined with type String, original " +
"definition at [testcode]:1 with type (Number|null)");
}
public void testScoping5() throws Exception {
// multiple definitions are not checked by the type checker but by a
// subsequent pass
testTypes("if (true) var b; var b;");
}
public void testScoping6() throws Exception {
// multiple definitions are not checked by the type checker but by a
// subsequent pass
testTypes("if (true) var b; if (true) var b;");
}
public void testScoping7() throws Exception {
testTypes("/** @constructor */function A() {" +
" /** @type !A */this.a = null;" +
"}",
"assignment to property a of A\n" +
"found : null\n" +
"required: A");
}
public void testScoping8() throws Exception {
testTypes("/** @constructor */function A() {}" +
"/** @constructor */function B() {" +
" /** @type !A */this.a = null;" +
"}",
"assignment to property a of B\n" +
"found : null\n" +
"required: A");
}
public void testScoping9() throws Exception {
testTypes("/** @constructor */function B() {" +
" /** @type !A */this.a = null;" +
"}" +
"/** @constructor */function A() {}",
"assignment to property a of B\n" +
"found : null\n" +
"required: A");
}
public void testScoping10() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope("var a = function b(){};");
// a declared, b is not
assertTrue(p.scope.isDeclared("a", false));
assertFalse(p.scope.isDeclared("b", false));
// checking that a has the correct assigned type
assertEquals("function (): undefined",
p.scope.getVar("a").getType().toString());
}
public void testScoping11() throws Exception {
// named function expressions create a binding in their body only
// the return is wrong but the assignment is ok since the type of b is ?
testTypes(
"/** @return {number} */var a = function b(){ return b };",
"inconsistent return type\n" +
"found : function (): number\n" +
"required: number");
}
public void testScoping12() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** @type {number} */ F.prototype.bar = 3;" +
"/** @param {!F} f */ function g(f) {" +
" /** @return {string} */" +
" function h() {" +
" return f.bar;" +
" }" +
"}",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testFunctionArguments1
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> Object");
}
public void testPropAccess() throws Exception {
testTypes("/** @param {*} x */var f = function(x) {\n" +
"var o = String(x);\n" +
"if (typeof o['a'] != 'undefined') { return o['a']; }\n" +
"return null;\n" +
"};");
}
public void testPropAccess2() throws Exception {
testTypes("var bar = void 0; bar.baz;",
"undefined has no properties\n" +
"found : undefined\n" +
"required: Object");
}
public void testPropAccess3() throws Exception {
// Verifies that we don't emit two warnings, because
// the var has been dereferenced after the first one.
testTypes("var bar = void 0; bar.baz; bar.bax;",
"undefined has no properties\n" +
"found : undefined\n" +
"required: Object");
}
public void testPropAccess4() throws Exception {
testTypes("/** @param {*} x */ function f(x) { return x['hi']; }");
}
public void testSwitchCase1() throws Exception {
testTypes("/**@type number*/var a;" +
"/**@type string*/var b;" +
"switch(a){case b:;}",
"case expression doesn't match switch\n" +
"found : string\n" +
"required: number");
}
public void testSwitchCase2() throws Exception {
testTypes("var a = null; switch (typeof a) { case 'foo': }");
}
public void testVar1() throws Exception {
TypeCheckResult p =
parseAndTypeCheckWithScope("/** @type {(string,null)} */var a = null");
assertEquals(createUnionType(STRING_TYPE, NULL_TYPE),
p.scope.getVar("a").getType());
}
public void testVar2() throws Exception {
testTypes("/** @type {Function} */ var a = function(){}");
}
public void testVar3() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope("var a = 3;");
assertEquals(NUMBER_TYPE, p.scope.getVar("a").getType());
}
public void testVar4() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var a = 3; a = 'string';");
assertEquals(createUnionType(STRING_TYPE, NUMBER_TYPE),
p.scope.getVar("a").getType());
}
public void testVar5() throws Exception {
testTypes("var goog = {};" +
"/** @type string */goog.foo = 'hello';" +
"/** @type number */var a = goog.foo;",
"initializing variable\n" +
"found : string\n" +
"required: number");
}
public void testVar6() throws Exception {
testTypes(
"function f() {" +
" return function() {" +
" /** @type {!Date} */" +
" var a = 7;" +
" };
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> @constructor \n @extends Element */" +
"function DIVElement() {};",
"(new DIVElement).innerHTML = new Array();",
null, false);
}
public void testImplicitCastNotInExterns() throws Exception {
testTypes("/** @constructor */ function Element() {};\n" +
"/** @type {string}\n" +
" * @implicitCast */" +
"Element.prototype.innerHTML;" +
"(new Element).innerHTML = new Array();",
new String[] {
"Illegal annotation on innerHTML. @implicitCast may only be " +
"used in externs.",
"assignment to property innerHTML of Element\n" +
"found : Array\n" +
"required: string"});
}
public void testNumberNode() throws Exception {
Node n = typeCheck(Node.newNumber(0));
assertEquals(NUMBER_TYPE, n.getJSType());
}
public void testStringNode() throws Exception {
Node n = typeCheck(Node.newString("hello"));
assertEquals(STRING_TYPE, n.getJSType());
}
public void testBooleanNodeTrue() throws Exception {
Node trueNode = typeCheck(new Node(Token.TRUE));
assertEquals(BOOLEAN_TYPE, trueNode.getJSType());
}
public void testBooleanNodeFalse() throws Exception {
Node falseNode = typeCheck(new Node(Token.FALSE));
assertEquals(BOOLEAN_TYPE, falseNode.getJSType());
}
public void testUndefinedNode() throws Exception {
Node p = new Node(Token.ADD);
Node n = Node.newString(Token.NAME, "undefined");
p.addChildToBack(n);
p.addChildToBack(Node.newNumber(5));
typeCheck(p);
assertEquals(VOID_TYPE, n.getJSType());
}
public void testNumberAutoboxing() throws Exception {
testTypes("/** @type Number */var a = 4;",
"initializing variable\n" +
"found : number\n" +
"required: (Number|null)");
}
public void testNumberUnboxing() throws Exception {
testTypes("/** @type number */var a = new Number(4);",
"initializing variable\n" +
"found : Number\n" +
"required: number");
}
public void testStringAutoboxing() throws Exception {
testTypes("/** @type String */var a = 'hello';",
"initializing variable\n" +
"found : string\n" +
"required: (String|null)");
}
public void testStringUnboxing() throws Exception {
testTypes("/** @type string */var a = new String('hello');",
"initializing variable\n" +
"found : String\n" +
"required: string");
}
public void testBooleanAutoboxing() throws Exception {
testTypes("/** @type Boolean */var a = true;",
"initializing variable\n" +
"found : boolean\n" +
"required: (Boolean|null)");
}
public void testBooleanUnboxing() throws Exception {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> function () {};" +
"}");
}
/**
* Tests that undefined can be compared shallowly to a value of type
* (number,undefined) regardless of the side on which the undefined
* value is.
*/
public void testBug901455() throws Exception {
testTypes("/** @return {(number,undefined)} */ function a() { return 3; }" +
"var b = undefined === a()");
testTypes("/** @return {(number,undefined)} */ function a() { return 3; }" +
"var b = a() === undefined");
}
/**
* Tests that the match method of strings returns nullable arrays.
*/
public void testBug908701() throws Exception {
testTypes("/** @type {String} */var s = new String('foo');" +
"var b = s.match(/a/) != null;");
}
/**
* Tests that named types play nicely with subtyping.
*/
public void testBug908625() throws Exception {
testTypes("/** @constructor */function A(){}" +
"/** @constructor\n * @extends A */function B(){}" +
"/** @param {B} b" +
"\n @return {(A,undefined)} */function foo(b){return b}");
}
/**
* Tests that assigning two untyped functions to a variable whose type is
* inferred and calling this variable is legal.
*/
public void testBug911118() throws Exception {
// verifying the type assigned to function expressions assigned variables
Scope s = parseAndTypeCheckWithScope("var a = function(){};").scope;
JSType type = s.getVar("a").getType();
assertEquals("function (): undefined", type.toString());
// verifying the bug example
testTypes("function nullFunction() {};" +
"var foo = nullFunction;" +
"foo = function() {};" +
"foo();");
}
public void testBug909000() throws Exception {
testTypes("/** @constructor */function A(){}\n" +
"/** @param {!A} a\n" +
"@return {boolean}*/\n" +
"function y(a) { return a }",
"inconsistent return type\n" +
"found : A\n" +
"required: boolean");
}
public void testBug930117() throws Exception {
testTypes(
"/** @param {boolean} x */function f(x){}" +
"f(null);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : null\n" +
"required: boolean");
}
public void testBug1484445() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @type {number?} */ Foo.prototype.bar = null;" +
"/** @type {number?} */ Foo.prototype.baz = null;" +
"/** @param {Foo} foo */" +
"function f(foo) {" +
" while (true) {" +
" if
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> assign = n.getLastChild().getFirstChild();
Node node = assign.getFirstChild();
assertFalse(node.getJSType().isUnknownType());
assertEquals("number", node.getJSType().toString());
}
public void testAssignToUntypedProperty() throws Exception {
Node n = parseAndTypeCheck(
"/** @constructor */ function Foo() {}\n" +
"Foo.prototype.a = 1;" +
"(new Foo).a;");
Node node = n.getLastChild().getFirstChild();
assertFalse(node.getJSType().isUnknownType());
assertTrue(node.getJSType().isNumber());
}
public void testNew1() throws Exception {
testTypes("new 4", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew2() throws Exception {
testTypes("var Math = {}; new Math()", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew3() throws Exception {
testTypes("new Date()");
}
public void testNew4() throws Exception {
testTypes("/** @constructor */function A(){}; new A();");
}
public void testNew5() throws Exception {
testTypes("function A(){}; new A();", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew6() throws Exception {
TypeCheckResult p =
parseAndTypeCheckWithScope("/** @constructor */function A(){};" +
"var a = new A();");
JSType aType = p.scope.getVar("a").getType();
assertTrue(aType instanceof ObjectType);
ObjectType aObjectType = (ObjectType) aType;
assertEquals("A", aObjectType.getConstructor().getReferenceName());
}
public void testNew7() throws Exception {
testTypes("/** @param {Function} opt_constructor */" +
"function foo(opt_constructor) {" +
"if (opt_constructor) { new opt_constructor; }" +
"}");
}
public void testNew8() throws Exception {
testTypes("/** @param {Function} opt_constructor */" +
"function foo(opt_constructor) {" +
"new opt_constructor;" +
"}");
}
public void testNew9() throws Exception {
testTypes("/** @param {Function} opt_constructor */" +
"function foo(opt_constructor) {" +
"new (opt_constructor || Array);" +
"}");
}
public void testNew10() throws Exception {
testTypes("var goog = {};" +
"/** @param {Function} opt_constructor */" +
"goog.Foo = function (opt_constructor) {" +
"new (opt_constructor || Array);" +
"}");
}
public void testNew11() throws Exception {
testTypes("/** @param {Function} c1 */" +
"function f(c1) {" +
" var c2 = function(){};" +
" c1.prototype = new c2;" +
"}", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew12() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope("var a = new Array();");
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> Var a = p.scope.getVar("a");
assertEquals(ARRAY_TYPE, a.getType());
}
public void testNew13() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"/** @constructor */function FooBar(){};" +
"var a = new FooBar();");
Var a = p.scope.getVar("a");
assertTrue(a.getType() instanceof ObjectType);
assertEquals("FooBar", a.getType().toString());
}
public void testNew14() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"/** @constructor */var FooBar = function(){};" +
"var a = new FooBar();");
Var a = p.scope.getVar("a");
assertTrue(a.getType() instanceof ObjectType);
assertEquals("FooBar", a.getType().toString());
}
public void testNew15() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var goog = {};" +
"/** @constructor */goog.A = function(){};" +
"var a = new goog.A();");
Var a = p.scope.getVar("a");
assertTrue(a.getType() instanceof ObjectType);
assertEquals("goog.A", a.getType().toString());
}
public void testNew16() throws Exception {
testTypes(
"/** \n" +
" * @param {string} x \n" +
" * @constructor \n" +
" */" +
"function Foo(x) {}" +
"function g() { new Foo(1); }",
"actual parameter 1 of Foo does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testName1() throws Exception {
assertEquals(VOID_TYPE, testNameNode("undefined"));
}
public void testName2() throws Exception {
assertEquals(OBJECT_FUNCTION_TYPE, testNameNode("Object"));
}
public void testName3() throws Exception {
assertEquals(ARRAY_FUNCTION_TYPE, testNameNode("Array"));
}
public void testName4() throws Exception {
assertEquals(DATE_FUNCTION_TYPE, testNameNode("Date"));
}
public void testName5() throws Exception {
assertEquals(REGEXP_FUNCTION_TYPE, testNameNode("RegExp"));
}
/**
* Type checks a NAME node and retrieve its type.
*/
private JSType testNameNode(String name) {
Node node = Node.newString(Token.NAME, name);
Node parent = new Node(Token.SCRIPT, node);
Node externs = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externs, parent);
externAndJsRoot.setIsSyntheticBlock(true);
makeTypeCheck().processForTesting(null, parent);
return node.getJSType();
}
public void testBitOperation1() throws Exception {
testTypes("/**@return {void}*/function foo(){ ~foo(); }",
"operator ~ cannot be applied to undefined");
}
public void testBitOperation2() throws Exception {
testTypes("/**@return {void
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>param {bar} x */\n" +
" function f(x) {}\n" +
"}");
}
public void testConstructorType7() throws Exception {
TypeCheckResult p =
parseAndTypeCheckWithScope("/** @constructor */function A(){};");
JSType type = p.scope.getVar("A").getType();
assertTrue(type instanceof FunctionType);
FunctionType fType = (FunctionType) type;
assertEquals("A", fType.getReferenceName());
}
public void testConstructorType8() throws Exception {
testTypes(
"var ns = {};" +
"ns.create = function() { return function() {}; };" +
"/** @constructor */ ns.Foo = ns.create();" +
"ns.Foo.prototype = {x: 0, y: 0};" +
"/**\n" +
" * @param {ns.Foo} foo\n" +
" * @return {string}\n" +
" */\n" +
"function f(foo) {" +
" return foo.x;" +
"}",
"inconsistent return type\n" +
"found : number\n" +
"required: string");
}
public void testConstructorType9() throws Exception {
testTypes(
"var ns = {};" +
"ns.create = function() { return function() {}; };" +
"ns.extend = function(x) { return x; };" +
"/** @constructor */ ns.Foo = ns.create();" +
"ns.Foo.prototype = ns.extend({x: 0, y: 0});" +
"/**\n" +
" * @param {ns.Foo} foo\n" +
" * @return {string}\n" +
" */\n" +
"function f(foo) {" +
" return foo.x;" +
"}");
}
public void testAnonymousType1() throws Exception {
testTypes("function f() {}" +
"/** @constructor */\n" +
"f().bar = function() {};");
}
public void testAnonymousType2() throws Exception {
testTypes("function f() {}" +
"/** @interface */\n" +
"f().bar = function() {};");
}
public void testAnonymousType3() throws Exception {
testTypes("function f() {}" +
"/** @enum */\n" +
"f().bar = {FOO: 1};");
}
public void testBang1() throws Exception {
testTypes("/** @param {Object} x\n@return {!Object} */\n" +
"function f(x) { return x; }",
"inconsistent return type\n" +
"found : (Object|null)\n" +
"required: Object");
}
public void testBang2() throws Exception {
testTypes("/** @param {Object} x\n@return {!Object} */\n" +
"function f(x) { return x ? x : new Object(); }");
}
public void testBang3() throws Exception {
testTypes("
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> new c;\n" +
"}");
}
public void testNamespacedConstructor() throws Exception {
Node root = parseAndTypeCheck(
"var goog = {};" +
"/** @constructor */ goog.MyClass = function() {};" +
"/** @return {!goog.MyClass} */ " +
"function foo() { return new goog.MyClass(); }");
JSType typeOfFoo = root.getLastChild().getJSType();
assert(typeOfFoo instanceof FunctionType);
JSType retType = ((FunctionType) typeOfFoo).getReturnType();
assert(retType instanceof ObjectType);
assertEquals("goog.MyClass", ((ObjectType) retType).getReferenceName());
}
public void testComplexNamespace() throws Exception {
String js =
"var goog = {};" +
"goog.foo = {};" +
"goog.foo.bar = 5;";
TypeCheckResult p = parseAndTypeCheckWithScope(js);
// goog type in the scope
JSType googScopeType = p.scope.getVar("goog").getType();
assertTrue(googScopeType instanceof ObjectType);
assertTrue("foo property not present on goog type",
((ObjectType) googScopeType).hasProperty("foo"));
assertFalse("bar property present on goog type",
((ObjectType) googScopeType).hasProperty("bar"));
// goog type on the VAR node
Node varNode = p.root.getFirstChild();
assertEquals(Token.VAR, varNode.getType());
JSType googNodeType = varNode.getFirstChild().getJSType();
assertTrue(googNodeType instanceof ObjectType);
// goog scope type and goog type on VAR node must be the same
assertTrue(googScopeType == googNodeType);
// goog type on the left of the GETPROP node (under fist ASSIGN)
Node getpropFoo1 = varNode.getNext().getFirstChild().getFirstChild();
assertEquals(Token.GETPROP, getpropFoo1.getType());
assertEquals("goog", getpropFoo1.getFirstChild().getString());
JSType googGetpropFoo1Type = getpropFoo1.getFirstChild().getJSType();
assertTrue(googGetpropFoo1Type instanceof ObjectType);
// still the same type as the one on the variable
assertTrue(googGetpropFoo1Type == googScopeType);
// the foo property should be defined on goog
JSType googFooType = ((ObjectType) googScopeType).getPropertyType("foo");
assertTrue(googFooType instanceof ObjectType);
// goog type on the left of the GETPROP lower level node
// (under second ASSIGN)
Node getpropFoo2 = varNode.getNext().getNext()
.getFirstChild().getFirstChild().getFirstChild();
assertEquals(Token.GETPROP, getpropFoo2.getType());
assertEquals("goog", getpropFoo2.getFirstChild().getString());
JSType googGetpropFoo2Type = getpropFoo2.getFirstChild().getJSType();
assertTrue(googGetpropFoo2Type instanceof ObjectType);
// still the same type as the one on the variable
assertTrue(googGetpropFoo2Type == googScopeType);
// goog.foo type on the left of the top level GETPROP node
// (under second ASSIGN)
JSType googFooGetprop2Type = getpropFoo
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>2.getJSType();
assertTrue("goog.foo incorrectly annotated in goog.foo.bar selection",
googFooGetprop2Type instanceof ObjectType);
ObjectType googFooGetprop2ObjectType = (ObjectType) googFooGetprop2Type;
assertFalse("foo property present on goog.foo type",
googFooGetprop2ObjectType.hasProperty("foo"));
assertTrue("bar property not present on goog.foo type",
googFooGetprop2ObjectType.hasProperty("bar"));
assertEquals("bar property on goog.foo type incorrectly inferred",
NUMBER_TYPE, googFooGetprop2ObjectType.getPropertyType("bar"));
}
public void testAddingMethodsUsingPrototypeIdiomSimpleNamespace()
throws Exception {
Node js1Node = parseAndTypeCheck(
"/** @constructor */function A() {}" +
"A.prototype.m1 = 5");
ObjectType instanceType = getInstanceType(js1Node);
assertEquals(NATIVE_PROPERTIES_COUNT + 1,
instanceType.getPropertiesCount());
checkObjectType(instanceType, "m1", NUMBER_TYPE);
}
public void testAddingMethodsUsingPrototypeIdiomComplexNamespace1()
throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var goog = {};" +
"goog.A = /** @constructor */function() {};" +
"/** @type number */goog.A.prototype.m1 = 5");
testAddingMethodsUsingPrototypeIdiomComplexNamespace(p);
}
public void testAddingMethodsUsingPrototypeIdiomComplexNamespace2()
throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var goog = {};" +
"/** @constructor */goog.A = function() {};" +
"/** @type number */goog.A.prototype.m1 = 5");
testAddingMethodsUsingPrototypeIdiomComplexNamespace(p);
}
private void testAddingMethodsUsingPrototypeIdiomComplexNamespace(
TypeCheckResult p) {
ObjectType goog = (ObjectType) p.scope.getVar("goog").getType();
assertEquals(NATIVE_PROPERTIES_COUNT + 1, goog.getPropertiesCount());
JSType googA = goog.getPropertyType("A");
assertNotNull(googA);
assertTrue(googA instanceof FunctionType);
FunctionType googAFunction = (FunctionType) googA;
ObjectType classA = googAFunction.getInstanceType();
assertEquals(NATIVE_PROPERTIES_COUNT + 1, classA.getPropertiesCount());
checkObjectType(classA, "m1", NUMBER_TYPE);
}
public void testAddingMethodsPrototypeIdiomAndObjectLiteralSimpleNamespace()
throws Exception {
Node js1Node = parseAndTypeCheck(
"/** @constructor */function A() {}" +
"A.prototype = {m1: 5, m2: true}");
ObjectType instanceType = getInstanceType(js1Node);
assertEquals(NATIVE_PROPERTIES_COUNT + 2,
instanceType.getPropertiesCount());
checkObjectType(instanceType, "m1", NUMBER_TYPE);
checkObjectType(instanceType, "m2", BOOLEAN_TYPE);
}
public void testDontAddMethodsIfNoConstructor()
throws Exception {
Node js1Node = parseAndTypeCheck(
"function A() {}"
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> n = parseAndTypeCheck("var a = {m1: 7, m2: 'hello'}");
Node nameNode = n.getFirstChild().getFirstChild();
Node objectNode = nameNode.getFirstChild();
// node extraction
assertEquals(Token.NAME, nameNode.getType());
assertEquals(Token.OBJECTLIT, objectNode.getType());
// value's type
ObjectType objectType =
(ObjectType) objectNode.getJSType();
assertEquals(NUMBER_TYPE, objectType.getPropertyType("m1"));
assertEquals(STRING_TYPE, objectType.getPropertyType("m2"));
// variable's type
assertEquals(objectType, nameNode.getJSType());
}
public void testObjectLiteralDeclaration1() throws Exception {
testTypes(
"var x = {" +
"/** @type {boolean} */ abc: true," +
"/** @type {number} */ 'def': 0," +
"/** @type {string} */ 3: 'fgh'" +
"};");
}
public void testObjectLiteralDeclaration2() throws Exception {
testTypes(
"var x = {" +
" /** @type {boolean} */ abc: true" +
"};" +
"x.abc = 0;",
"assignment to property abc of x\n" +
"found : number\n" +
"required: boolean");
}
public void testObjectLiteralDeclaration3() throws Exception {
testTypes(
"/** @param {{foo: !Function}} x */ function f(x) {}" +
"f({foo: function() {}});");
}
public void testObjectLiteralDeclaration4() throws Exception {
testClosureTypesMultipleWarnings(
"var x = {" +
" /** @param {boolean} x */ abc: function(x) {}" +
"};" +
"/**\n" +
" * @param {string} x\n" +
" * @suppress {duplicate}\n" +
" */ x.abc = function(x) {};",
Lists.newArrayList(
"variable x.abc redefined with type " +
"function (string): undefined, " +
"original definition at [testcode] :1 with type " +
"function (boolean): undefined",
"assignment to property abc of x\n" +
"found : function (string): undefined\n" +
"required: function (boolean): undefined"));
}
public void testObjectLiteralDeclaration5() throws Exception {
testTypes(
"var x = {" +
" /** @param {boolean} x */ abc: function(x) {}" +
"};" +
"/**\n" +
" * @param {boolean} x\n" +
" * @suppress {duplicate}\n" +
" */ x.abc = function(x) {};");
}
public void testObjectLiteralDeclaration6() throws Exception {
testTypes(
"var x = {};" +
"/**\n" +
" * @param {boolean} x\n" +
" * @suppress {duplicate}\n" +
" */ x.abc = function(x) {};" +
"x
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
"/** @param {AB} x */ function f(x) {}" +
"/** @constructor */ function A() {}" +
"/** @constructor */ function B() {}" +
"/** @typedef {(A|B)} */ var AB;" +
"f(new A()); f(new B()); f(1);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : number\n" +
"required: (A|B|null)");
}
public void testCircularTypeDef() throws Exception {
testTypes(
"var goog = {};" +
"/** @typedef {number|Array.<goog.Bar>} */ goog.Bar;" +
"/** @param {goog.Bar} x */ function f(x) {}" +
"f(3); f([3]); f([[3]]);");
}
public void testGetTypedPercent1() throws Exception {
String js = "var id = function(x) { return x; }\n" +
"var id2 = function(x) { return id(x); }";
assertEquals(50.0, getTypedPercent(js), 0.1);
}
public void testGetTypedPercent2() throws Exception {
String js = "var x = {}; x.y = 1;";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
public void testGetTypedPercent3() throws Exception {
String js = "var f = function(x) { x.a = x.b; }";
assertEquals(50.0, getTypedPercent(js), 0.1);
}
public void testGetTypedPercent4() throws Exception {
String js = "var n = {};\n /** @constructor */ n.T = function() {};\n" +
"/** @type n.T */ var x = new n.T();";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
private double getTypedPercent(String js) throws Exception {
Node n = compiler.parseTestCode(js);
Node externs = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externs, n);
externAndJsRoot.setIsSyntheticBlock(true);
TypeCheck t = makeTypeCheck();
t.processForTesting(null, n);
return t.getTypedPercent();
}
private ObjectType getInstanceType(Node js1Node) {
JSType type = js1Node.getFirstChild().getJSType();
assertNotNull(type);
assertTrue(type instanceof FunctionType);
FunctionType functionType = (FunctionType) type;
assertTrue(functionType.isConstructor());
return functionType.getInstanceType();
}
public void testPrototypePropertyReference() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(""
+ "/** @constructor */\n"
+ "function Foo() {}\n"
+ "/** @param {number} a */\n"
+ "Foo.prototype.bar = function(a){};\n"
+ "/** @param {Foo} f */\n"
+ "function baz(f
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>) {\n"
+ " Foo.prototype.bar.call(f, 3);\n"
+ "}");
assertEquals(0, compiler.getErrorCount());
assertEquals(0, compiler.getWarningCount());
assertTrue(p.scope.getVar("Foo").getType() instanceof FunctionType);
FunctionType fooType = (FunctionType) p.scope.getVar("Foo").getType();
assertEquals("function (this:Foo, number): undefined",
fooType.getPrototype().getPropertyType("bar").toString());
}
public void testResolvingNamedTypes() throws Exception {
String js = ""
+ "/** @constructor */\n"
+ "var Foo = function() {}\n"
+ "/** @param {number} a */\n"
+ "Foo.prototype.foo = function(a) {\n"
+ " return this.baz().toString();\n"
+ "};\n"
+ "/** @return {Baz} */\n"
+ "Foo.prototype.baz = function() { return new Baz(); };\n"
+ "/** @constructor\n"
+ " * @extends Foo */\n"
+ "var Bar = function() {};"
+ "/** @constructor */\n"
+ "var Baz = function() {};";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
public void testMissingProperty1() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"Foo.prototype.baz = function() { this.a = 3; };");
}
public void testMissingProperty2() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"Foo.prototype.baz = function() { this.b = 3; };",
"Property a never defined on Foo");
}
public void testMissingProperty3() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"(new Foo).a = 3;");
}
public void testMissingProperty4() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"(new Foo).b = 3;",
"Property a never defined on Foo");
}
public void testMissingProperty5() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"/** @constructor */ function Bar() { this.a = 3; };",
"Property a never defined on Foo");
}
public void testMissingProperty6() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"Foo.prototype.bar = function() { return this.a; };" +
"/** @constructor
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> + f
assertEquals(registry.getNativeType(JSTypeNative.NUMBER_TYPE),
n.getFirstChild().getLastChild().getLastChild().getFirstChild()
.getNext().getFirstChild().getJSType());
}
public void testFlowScopeBug2() throws Exception {
Node n = parseAndTypeCheck("/** @constructor */ function Foo() {};\n"
+ "Foo.prototype.hi = false;"
+ "function foo(a, b) {\n"
+ " /** @type Array */"
+ " var arr;"
+ " /** @type number */"
+ " var iter;"
+ " for (iter = 0; iter < arr.length; ++ iter) {"
+ " /** @type Foo */"
+ " var afoo = arr[iter];"
+ " afoo;"
+ " }"
+ "}");
// check the type of afoo when referenced
assertEquals(registry.createNullableType(registry.getType("Foo")),
n.getLastChild().getLastChild().getLastChild().getLastChild()
.getLastChild().getLastChild().getJSType());
}
public void testAddSingletonGetter() {
Node n = parseAndTypeCheck(
"/** @constructor */ function Foo() {};\n" +
"goog.addSingletonGetter(Foo);");
ObjectType o = (ObjectType) n.getFirstChild().getJSType();
assertEquals("function (): Foo",
o.getPropertyType("getInstance").toString());
assertEquals("Foo", o.getPropertyType("instance_").toString());
}
public void testTypeCheckStandaloneAST() throws Exception {
Node n = compiler.parseTestCode("function Foo() { }");
typeCheck(n);
TypedScopeCreator scopeCreator = new TypedScopeCreator(compiler);
Scope topScope = scopeCreator.createScope(n, null);
Node second = compiler.parseTestCode("new Foo");
Node externs = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externs, second);
externAndJsRoot.setIsSyntheticBlock(true);
new TypeCheck(
compiler,
new SemanticReverseAbstractInterpreter(
compiler.getCodingConvention(), registry),
registry, topScope, scopeCreator, CheckLevel.WARNING, CheckLevel.OFF)
.process(null, second);
assertEquals(1, compiler.getWarningCount());
assertEquals("cannot instantiate non-constructor",
compiler.getWarnings()[0].description);
}
public void testUpdateParameterTypeOnClosure() throws Exception {
testTypes(
"/**\n" +
"* @constructor\n" +
"* @param {*=} opt_value\n" +
"* @return {?}\n" +
"*/\n" +
"function Object(opt_value) {}\n" +
"/**\n" +
"* @constructor\n" +
"* @param {...*} var_args\n" +
"*/\n" +
"function Function(var_args) {}\n" +
"/**\n" +
"* @type {Function}\n" +
"*/\n" +
// The line below sets JSDocInfo on Object so that
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
"function Int5() {};",
new String[] {
"Interface Int5 has a property bar with incompatible types in its" +
" super interfaces Int1 and Int4",
"Interface Int5 has a property foo with incompatible types in its" +
" super interfaces Int0 and Int4"});
}
private void testTypes(String js) throws Exception {
testTypes(js, (String) null);
}
private void testTypes(String js, String description) throws Exception {
testTypes(js, description, false);
}
private void testTypes(String js, DiagnosticType type) throws Exception {
testTypes(js, type.format(), false);
}
private void testClosureTypes(String js, String description)
throws Exception {
testClosureTypesMultipleWarnings(js,
description == null ? null : Lists.newArrayList(description));
}
private void testClosureTypesMultipleWarnings(
String js, List<String> descriptions) throws Exception {
Node n = compiler.parseTestCode(js);
Node externs = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externs, n);
externAndJsRoot.setIsSyntheticBlock(true);
assertEquals("parsing error: " +
Joiner.on(", ").join(compiler.getErrors()),
0, compiler.getErrorCount());
// For processing goog.addDependency for forward typedefs.
new ProcessClosurePrimitives(compiler, CheckLevel.ERROR, true)
.process(null, n);
CodingConvention convention = compiler.getCodingConvention();
new TypeCheck(compiler,
new ClosureReverseAbstractInterpreter(
convention, registry).append(
new SemanticReverseAbstractInterpreter(
convention, registry))
.getFirst(),
registry)
.processForTesting(null, n);
assertEquals(0, compiler.getErrorCount());
if (descriptions == null) {
assertEquals(
"unexpected warning(s) : " +
Joiner.on(", ").join(compiler.getWarnings()),
0, compiler.getWarningCount());
} else {
assertEquals(
"unexpected warning(s) : " +
Joiner.on(", ").join(compiler.getWarnings()),
descriptions.size(), compiler.getWarningCount());
for (int i = 0; i < descriptions.size(); i++) {
assertEquals(descriptions.get(i),
compiler.getWarnings()[i].description);
}
}
}
void testTypes(String js, String description, boolean isError)
throws Exception {
testTypes(DEFAULT_EXTERNS, js, description, isError);
}
void testTypes(String externs, String js, String description, boolean isError)
throws Exception {
Node n = parseAndTypeCheck(externs, js);
JSError[] errors = compiler.getErrors();
if (description != null && isError) {
assertTrue("expected an error", errors.length > 0);
assertEquals(description, errors[0].description);
errors = Arrays.asList(errors).subList(1, errors.length).toArray(
new JSError[errors.length - 1]);
}
if (errors.length > 0) {
fail("unexpected error(s
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>):\n" + Joiner.on("\n").join(errors));
}
JSError[] warnings = compiler.getWarnings();
if (description != null && !isError) {
assertTrue("expected a warning", warnings.length > 0);
assertEquals(description, warnings[0].description);
warnings = Arrays.asList(warnings).subList(1, warnings.length).toArray(
new JSError[warnings.length - 1]);
}
if (warnings.length > 0) {
fail("unexpected warnings(s):\n" + Joiner.on("\n").join(warnings));
}
}
/**
* Parses and type checks the JavaScript code.
*/
private Node parseAndTypeCheck(String js) {
return parseAndTypeCheck(DEFAULT_EXTERNS, js);
}
private Node parseAndTypeCheck(String externs, String js) {
return parseAndTypeCheckWithScope(externs, js).root;
}
/**
* Parses and type checks the JavaScript code and returns the Scope used
* whilst type checking.
*/
private TypeCheckResult parseAndTypeCheckWithScope(String js) {
return parseAndTypeCheckWithScope(DEFAULT_EXTERNS, js);
}
private TypeCheckResult parseAndTypeCheckWithScope(
String externs, String js) {
compiler.init(
Lists.newArrayList(JSSourceFile.fromCode("[externs]", externs)),
Lists.newArrayList(JSSourceFile.fromCode("[testcode]", js)),
compiler.getOptions());
Node n = compiler.getInput("[testcode]").getAstRoot(compiler);
Node externsNode = compiler.getInput("[externs]").getAstRoot(compiler);
Node externAndJsRoot = new Node(Token.BLOCK, externsNode, n);
externAndJsRoot.setIsSyntheticBlock(true);
assertEquals("parsing error: " +
Joiner.on(", ").join(compiler.getErrors()),
0, compiler.getErrorCount());
Scope s = makeTypeCheck().processForTesting(externsNode, n);
return new TypeCheckResult(n, s);
}
private Node typeCheck(Node n) {
Node externsNode = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externsNode, n);
externAndJsRoot.setIsSyntheticBlock(true);
makeTypeCheck().processForTesting(null, n);
return n;
}
private TypeCheck makeTypeCheck() {
return new TypeCheck(
compiler,
new SemanticReverseAbstractInterpreter(
compiler.getCodingConvention(), registry),
registry,
reportMissingOverrides,
CheckLevel.OFF);
}
void testTypes(String js, String[] warnings) throws Exception {
Node n = compiler.parseTestCode(js);
assertEquals(0, compiler.getErrorCount());
Node externsNode = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externsNode, n);
makeTypeCheck().processForTesting(null, n);
assertEquals(0, compiler.getErrorCount());
if (warnings != null) {
assertEquals(warnings.length, compiler.getWarningCount());
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>();
if (value != null && value.getType() == Token.FUNCTION) {
Preconditions.checkState(
!nodePriorities.containsKey(candidate) || candidate == entry);
prioritizeFromEntryNode(candidate);
}
}
}
// At this point, all reachable nodes have been given a priority, but
// unreachable nodes have not been given a priority. Put them last.
// Presumably, it doesn't really matter what priority they get, since
// this shouldn't happen in real code.
for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) {
if (!nodePriorities.containsKey(candidate)) {
nodePriorities.put(candidate, ++priorityCounter);
}
}
// Again, the implicit return node is always last.
nodePriorities.put(cfg.getImplicitReturn(), ++priorityCounter);
}
/**
* Given an entry node, find all the nodes reachable from that node
* and prioritize them.
*/
private void prioritizeFromEntryNode(DiGraphNode<Node, Branch> entry) {
PriorityQueue<DiGraphNode<Node, Branch>> worklist =
new PriorityQueue<DiGraphNode<Node, Branch>>(10, priorityComparator);
worklist.add(entry);
while (!worklist.isEmpty()) {
DiGraphNode<Node, Branch> current = worklist.remove();
if (nodePriorities.containsKey(current)) {
continue;
}
nodePriorities.put(current, ++priorityCounter);
List<DiGraphNode<Node, Branch>> successors =
cfg.getDirectedSuccNodes(current);
for (DiGraphNode<Node, Branch> candidate : successors) {
worklist.add(candidate);
}
}
}
@Override
public boolean shouldTraverse(
NodeTraversal nodeTraversal, Node n, Node parent) {
astPosition.put(n, astPositionCounter++);
switch (n.getType()) {
case Token.FUNCTION:
if (shouldTraverseFunctions || n == cfg.getEntry().getValue()) {
exceptionHandler.push(n);
return true;
}
return false;
case Token.TRY:
exceptionHandler.push(n);
return true;
}
/*
* We are going to stop the traversal depending on what the node's parent
* is.
*
* We are only interested in adding edges between nodes that change control
* flow. The most obvious ones are loops and IF-ELSE's. A statement
* transfers control to its next sibling.
*
* In case of an expression tree, there is no control flow within the tree
* even when there are short circuited operators and conditionals. When we
* are doing data flow analysis, we will simply synthesize lattices up the
* expression tree by finding the meet at each expression node.
*
* For example: within a Token.SWITCH, the expression in question does not
* change the control flow and need not to be considered.
*/
if (parent != null) {
switch (parent.getType()) {
case Token.FOR:
// Only traverse the body of the for loop.
return n == parent.getLastChild();
// Skip
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> the conditions.
case Token.IF:
case Token.WHILE:
case Token.WITH:
return n != parent.getFirstChild();
case Token.DO:
return n != parent.getFirstChild().getNext();
// Only traverse the body of the cases
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.LABEL:
return n != parent.getFirstChild();
case Token.FUNCTION:
return n == parent.getFirstChild().getNext().getNext();
case Token.CONTINUE:
case Token.BREAK:
case Token.EXPR_RESULT:
case Token.VAR:
case Token.RETURN:
case Token.THROW:
return false;
case Token.TRY:
/* Just before we are about to visit the second child of the TRY node,
* we know that we will be visiting either the CATCH or the FINALLY.
* In other words, we know that the post order traversal of the TRY
* block has been finished, no more exceptions can be caught by the
* handler at this TRY block and should be taken out of the stack.
*/
if (n == parent.getFirstChild().getNext()) {
Preconditions.checkState(exceptionHandler.peek() == parent);
exceptionHandler.pop();
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.IF:
handleIf(n);
return;
case Token.WHILE:
handleWhile(n);
return;
case Token.DO:
handleDo(n);
return;
case Token.FOR:
handleFor(n);
return;
case Token.SWITCH:
handleSwitch(n);
return;
case Token.CASE:
handleCase(n);
return;
case Token.DEFAULT:
handleDefault(n);
return;
case Token.BLOCK:
case Token.SCRIPT:
handleStmtList(n);
return;
case Token.FUNCTION:
handleFunction(n);
return;
case Token.EXPR_RESULT:
handleExpr(n);
return;
case Token.THROW:
handleThrow(n);
return;
case Token.TRY:
handleTry(n);
return;
case Token.CATCH:
handleCatch(n);
return;
case Token.BREAK:
handleBreak(n);
return;
case Token.CONTINUE:
handleContinue(n);
return;
case Token.RETURN:
handleReturn(n);
return;
case Token.WITH:
handleWith(n);
return;
case Token.LABEL:
return;
default:
handleStmt(n);
return;
}
}
private void handleIf(Node node) {
Node thenBlock = node.getFirstChild().getNext();
Node elseBlock = thenBlock.getNext();
createEdge(node, Branch.ON_TRUE, computeFallThrough(thenBlock));
if (elseBlock == null) {
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this)); // not taken branch
} else {
createEdge(node, Branch.ON_FALSE, compute
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>FallThrough(elseBlock));
}
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleWhile(Node node) {
// Control goes to the first statement if the condition evaluates to true.
createEdge(node, Branch.ON_TRUE,
computeFallThrough(node.getFirstChild().getNext()));
// Control goes to the follow() if the condition evaluates to false.
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this));
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleDo(Node node) {
// The first edge can be the initial iteration as well as the iterations
// after.
createEdge(node, Branch.ON_TRUE, computeFallThrough(node.getFirstChild()));
// The edge that leaves the do loop if the condition fails.
createEdge(node, Branch.ON_FALSE,
computeFollowNode(node, this));
connectToPossibleExceptionHandler(
node, NodeUtil.getConditionExpression(node));
}
private void handleFor(Node forNode) {
if (forNode.getChildCount() == 4) {
// We have for (init; cond; iter) { body }
Node init = forNode.getFirstChild();
Node cond = init.getNext();
Node iter = cond.getNext();
Node body = iter.getNext();
// After initialization, we transfer to the FOR which is in charge of
// checking the condition (for the first time).
createEdge(init, Branch.UNCOND, forNode);
// The edge that transfer control to the beginning of the loop body.
createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body));
// The edge to end of the loop.
createEdge(forNode, Branch.ON_FALSE,
computeFollowNode(forNode, this));
// The end of the body will have a unconditional branch to our iter
// (handled by calling computeFollowNode of the last instruction of the
// body. Our iter will jump to the forNode again to another condition
// check.
createEdge(iter, Branch.UNCOND, forNode);
connectToPossibleExceptionHandler(init, init);
connectToPossibleExceptionHandler(forNode, cond);
connectToPossibleExceptionHandler(iter, iter);
} else {
// We have for (item in collection) { body }
Node item = forNode.getFirstChild();
Node collection = item.getNext();
Node body = collection.getNext();
// The edge that transfer control to the beginning of the loop body.
createEdge(forNode, Branch.ON_TRUE, computeFallThrough(body));
// The edge to end of the loop.
createEdge(forNode, Branch.ON_FALSE,
computeFollowNode(forNode, this));
connectToPossibleExceptionHandler(forNode, collection);
}
}
private void handleSwitch(Node node) {
// Transfer to the first non-DEFAULT CASE. if there are none, transfer
// to the DEFAULT or the EMPTY node.
Node next = getNextSiblingOfType(
node.getFirstChild().getNext(), Token.CASE, Token.EMPTY);
if (next != null)
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> { // Has at least one CASE or EMPTY
createEdge(node, Branch.UNCOND, next);
} else { // Has no CASE but possibly a DEFAULT
if (node.getFirstChild().getNext() != null) {
createEdge(node, Branch.UNCOND, node.getFirstChild().getNext());
} else { // No CASE, no DEFAULT
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleCase(Node node) {
// Case is a bit tricky....First it goes into the body if condition is true.
createEdge(node, Branch.ON_TRUE,
node.getFirstChild().getNext());
// Look for the next CASE, skipping over DEFAULT.
Node next = getNextSiblingOfType(node.getNext(), Token.CASE);
if (next != null) { // Found a CASE
Preconditions.checkState(next.getType() == Token.CASE);
createEdge(node, Branch.ON_FALSE, next);
} else { // No more CASE found, go back and search for a DEFAULT.
Node parent = node.getParent();
Node deflt = getNextSiblingOfType(
parent.getFirstChild().getNext(), Token.DEFAULT);
if (deflt != null) { // Has a DEFAULT
createEdge(node, Branch.ON_FALSE, deflt);
} else { // No DEFAULT found, go to the follow of the SWITCH.
createEdge(node, Branch.ON_FALSE, computeFollowNode(node, this));
}
}
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleDefault(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleWith(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getLastChild());
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleStmtList(Node node) {
Node parent = node.getParent();
// Special case, don't add a block of empty CATCH block to the graph.
if (node.getType() == Token.BLOCK && parent != null &&
parent.getType() == Token.TRY &&
NodeUtil.getCatchBlock(parent) == node &&
!NodeUtil.hasCatchHandler(node)) {
return;
}
// A block transfer control to its first child if it is not empty.
Node child = node.getFirstChild();
// Function declarations are skipped since control doesn't go into that
// function (unless it is called)
while (child != null && child.getType() == Token.FUNCTION) {
child = child.getNext();
}
if (child != null) {
createEdge(node, Branch.UNCOND, computeFallThrough(child));
} else {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
}
// Synthetic blocks
if (parent != null) {
switch (parent.getType()) {
case
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> Token.DEFAULT:
case Token.CASE:
case Token.TRY:
break;
default:
if (node.getType() == Token.BLOCK && node.isSyntheticBlock()) {
createEdge(node, Branch.SYN_BLOCK, computeFollowNode(node, this));
}
break;
}
}
}
private void handleFunction(Node node) {
// A block transfer control to its first child if it is not empty.
Preconditions.checkState(node.getChildCount() >= 3);
createEdge(node, Branch.UNCOND,
computeFallThrough(node.getFirstChild().getNext().getNext()));
Preconditions.checkState(exceptionHandler.peek() == node);
exceptionHandler.pop();
}
private void handleExpr(Node node) {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
connectToPossibleExceptionHandler(node, node);
}
private void handleThrow(Node node) {
connectToPossibleExceptionHandler(node, node);
}
private void handleTry(Node node) {
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleCatch(Node node) {
createEdge(node, Branch.UNCOND, node.getLastChild());
}
private void handleBreak(Node node) {
String label = null;
// See if it is a break with label.
if (node.hasChildren()) {
label = node.getFirstChild().getString();
}
Node cur;
Node previous = null;
Node lastJump;
Node parent = node.getParent();
/*
* Continuously look up the ancestor tree for the BREAK target or the target
* with the corresponding label and connect to it. If along the path we
* discover a FINALLY, we will connect the BREAK to that FINALLY. From then
* on, we will just record the control flow changes in the finallyMap. This
* is due to the fact that we need to connect any node that leaves its own
* FINALLY block to the outer FINALLY or the BREAK's target but those nodes
* are not known yet due to the way we traverse the nodes.
*/
for (cur = node, lastJump = node;
!isBreakTarget(cur, label);
cur = parent, parent = parent.getParent()) {
if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)
&& cur.getLastChild() != previous) {
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFallThrough(
cur.getLastChild()));
} else {
finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
}
lastJump = cur;
}
Preconditions.checkState(parent != null, "Cannot find break target.");
previous = cur;
}
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, computeFollowNode(cur, this));
} else {
finallyMap.put(lastJump, computeFollowNode(cur, this));
}
}
private void handleContinue(Node node) {
String label = null;
if (node.hasChildren()) {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
label = node.getFirstChild().getString();
}
Node cur;
Node previous = null;
Node lastJump;
// Similar to handBreak's logic with a few minor variation.
Node parent = node.getParent();
for (cur = node, lastJump = node;
!isContinueTarget(cur, parent, label);
cur = parent, parent = parent.getParent()) {
if (cur.getType() == Token.TRY && NodeUtil.hasFinally(cur)
&& cur.getLastChild() != previous) {
if (lastJump == node) {
createEdge(lastJump, Branch.UNCOND, cur.getLastChild());
} else {
finallyMap.put(lastJump, computeFallThrough(cur.getLastChild()));
}
lastJump = cur;
}
Preconditions.checkState(parent != null, "Cannot find continue target.");
previous = cur;
}
Node iter = cur;
if (cur.getChildCount() == 4) {
iter = cur.getFirstChild().getNext().getNext();
}
if (lastJump == node) {
createEdge(node, Branch.UNCOND, iter);
} else {
finallyMap.put(lastJump, iter);
}
}
private void handleReturn(Node node) {
Node lastJump = null;
for (Iterator<Node> iter = exceptionHandler.iterator(); iter.hasNext();) {
Node curHandler = iter.next();
if (NodeUtil.isFunction(curHandler)) {
break;
}
if (NodeUtil.hasFinally(curHandler)) {
if (lastJump == null) {
createEdge(node, Branch.UNCOND, curHandler.getLastChild());
} else {
finallyMap.put(lastJump,
computeFallThrough(curHandler.getLastChild()));
}
lastJump = curHandler;
}
}
if (node.hasChildren()) {
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
if (lastJump == null) {
createEdge(node, Branch.UNCOND, null);
} else {
finallyMap.put(lastJump, null);
}
}
private void handleStmt(Node node) {
// Simply transfer to the next line.
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
connectToPossibleExceptionHandler(node, node);
}
static Node computeFollowNode(Node node, ControlFlowAnalysis cfa) {
return computeFollowNode(node, node, cfa);
}
static Node computeFollowNode(Node node) {
return computeFollowNode(node, node, null);
}
/**
* Computes the follow() node of a given node and its parent. There is a side
* effect when calling this function. If this function computed an edge that
* exists a FINALLY, it'll attempt to connect the fromNode to the outer
* FINALLY according to the finallyMap.
*
* @param fromNode The original source node since {@code node} is changed
* during recursion.
* @param node The node that follow() should compute.
*/
private static Node computeFollowNode(
Node fromNode, Node node, ControlFlowAnalysis cfa)
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> {
/*
* This is the case where:
*
* 1. Parent is null implies that we are transferring control to the end of
* the script.
*
* 2. Parent is a function implies that we are transferring control back to
* the caller of the function.
*
* 3. If the node is a return statement, we should also transfer control
* back to the caller of the function.
*
* 4. If the node is root then we have reached the end of what we have been
* asked to traverse.
*
* In all cases we should transfer control to a "symbolic return" node.
* This will make life easier for DFAs.
*/
Node parent = node.getParent();
if (parent == null || parent.getType() == Token.FUNCTION ||
(cfa != null && node == cfa.root)) {
return null;
}
// If we are just before a IF/WHILE/DO/FOR:
switch (parent.getType()) {
// The follow() of any of the path from IF would be what follows IF.
case Token.IF:
return computeFollowNode(fromNode, parent, cfa);
case Token.CASE:
case Token.DEFAULT:
// After the body of a CASE, the control goes to the body of the next
// case, without having to go to the case condition.
if (parent.getNext() != null) {
if (parent.getNext().getType() == Token.CASE) {
return parent.getNext().getFirstChild().getNext();
} else if (parent.getNext().getType() == Token.DEFAULT) {
return parent.getNext().getFirstChild();
} else {
Preconditions.checkState(false, "Not reachable");
}
} else {
return computeFollowNode(fromNode, parent, cfa);
}
break;
case Token.FOR:
if (NodeUtil.isForIn(parent)) {
return parent;
} else {
return parent.getFirstChild().getNext().getNext();
}
case Token.WHILE:
case Token.DO:
return parent;
case Token.TRY:
// If we are coming out of the TRY block...
if (parent.getFirstChild() == node) {
if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
return computeFallThrough(parent.getLastChild());
} else { // and have no FINALLY.
return computeFollowNode(fromNode, parent, cfa);
}
// CATCH block.
} else if (NodeUtil.getCatchBlock(parent) == node){
if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
return computeFallThrough(node.getNext());
} else {
return computeFollowNode(fromNode, parent, cfa);
}
// If we are coming out of the FINALLY block...
} else if (parent.getLastChild() == node){
if (cfa != null) {
for (Node finallyNode : cfa.finallyMap.get(parent)) {
cfa.createEdge(fromNode, Branch.UNCOND, finallyNode);
}
}
return computeFollowNode
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>(fromNode, parent, cfa);
}
}
// Now that we are done with the special cases follow should be its
// immediate sibling, unless its sibling is a function
Node nextSibling = node.getNext();
// Skip function declarations because control doesn't get pass into it.
while (nextSibling != null && nextSibling.getType() == Token.FUNCTION) {
nextSibling = nextSibling.getNext();
}
if (nextSibling != null) {
return computeFallThrough(nextSibling);
} else {
// If there are no more siblings, control is transfered up the AST.
return computeFollowNode(fromNode, parent, cfa);
}
}
/**
* Computes the destination node of n when we want to fallthough into the
* subtree of n. We don't always create a CFG edge into n itself because of
* DOs and FORs.
*/
static Node computeFallThrough(Node n) {
switch (n.getType()) {
case Token.DO:
return computeFallThrough(n.getFirstChild());
case Token.FOR:
if (NodeUtil.isForIn(n)) {
return n;
}
return computeFallThrough(n.getFirstChild());
case Token.LABEL:
return computeFallThrough(n.getLastChild());
default:
return n;
}
}
/**
* Connects the two nodes in the control flow graph.
*
* @param fromNode Source.
* @param toNode Destination.
*/
private void createEdge(Node fromNode, ControlFlowGraph.Branch branch,
Node toNode) {
cfg.createNode(fromNode);
cfg.createNode(toNode);
cfg.connectIfNotFound(fromNode, branch, toNode);
}
/**
* Connects cfgNode to the proper CATCH block if target subtree might throw
* an exception. If there are FINALLY blocks reached before a CATCH, it will
* make the corresponding entry in finallyMap.
*/
private void connectToPossibleExceptionHandler(Node cfgNode, Node target) {
if (mayThrowException(target) && !exceptionHandler.isEmpty()) {
Node lastJump = cfgNode;
for (Node handler : exceptionHandler) {
if (NodeUtil.isFunction(handler)) {
return;
}
Preconditions.checkState(handler.getType() == Token.TRY);
Node catchBlock = NodeUtil.getCatchBlock(handler);
if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, handler.getLastChild());
} else {
finallyMap.put(lastJump, handler.getLastChild());
}
} else { // Has a catch.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, catchBlock);
return;
} else {
finallyMap.put(lastJump, catchBlock);
}
}
lastJump = handler;
}
}
}
/**
* Get the next sibling (including itself) of one of the given types.
*/
private static Node getNextSiblingOfType(Node first, int ... types)
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> {
for (Node c = first; c != null; c = c.getNext()) {
for (int type : types) {
if (c.getType() == type) {
return c;
}
}
}
return null;
}
/**
* Checks if target is actually the break target of labeled continue. The
* label can be null if it is an unlabeled break.
*/
public static boolean isBreakTarget(Node target, String label) {
return isBreakStructure(target, label != null) &&
matchLabel(target.getParent(), label);
}
/**
* Checks if target is actually the continue target of labeled continue. The
* label can be null if it is an unlabeled continue.
*/
private static boolean isContinueTarget(
Node target, Node parent, String label) {
return isContinueStructure(target) && matchLabel(parent, label);
}
/**
* Check if label is actually referencing the target control structure. If
* label is null, it always returns true.
*/
private static boolean matchLabel(Node target, String label) {
if (label == null) {
return true;
}
while (target.getType() == Token.LABEL) {
if (target.getFirstChild().getString().equals(label)) {
return true;
}
target = target.getParent();
}
return false;
}
/**
* Determines if the subtree might throw an exception.
*/
public static boolean mayThrowException(Node n) {
switch (n.getType()) {
case Token.CALL:
case Token.GETPROP:
case Token.GETELEM:
case Token.THROW:
case Token.NEW:
case Token.ASSIGN:
case Token.INC:
case Token.DEC:
case Token.INSTANCEOF:
return true;
case Token.FUNCTION:
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) {
return true;
}
}
return false;
}
/**
* Determines whether the given node can be terminated with a BREAK node.
*/
static boolean isBreakStructure(Node n, boolean labeled) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.SWITCH:
return true;
case Token.BLOCK:
case Token.IF:
case Token.TRY:
return labeled;
default:
return false;
}
}
/**
* Determines whether the given node can be advanced with a CONTINUE node.
*/
static boolean isContinueStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* Get the TRY block with a CATCH that would be run if n throws an exception.
* @return The CATCH node or null if it there isn't a CATCH before the
* the function terminates.
*/
static Node getExceptionHandler(Node n)
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> {
for (Node cur = n;
cur.getType() != Token.SCRIPT && cur.getType() != Token.FUNCTION;
cur = cur.getParent()) {
Node catchNode = getCatchHandlerForBlock(cur);
if (catchNode != null) {
return catchNode;
}
}
return null;
}
/**
* Locate the catch BLOCK given the first block in a TRY.
* @return The CATCH node or null there is no catch handler.
*/
static Node getCatchHandlerForBlock(Node block) {
if (block.getType() == Token.BLOCK &&
block.getParent().getType() == Token.TRY &&
block.getParent().getFirstChild() == block) {
for (Node s = block.getNext(); s != null; s = s.getNext()) {
if (NodeUtil.hasCatchHandler(s)) {
return s.getFirstChild();
}
}
}
return null;
}
/**
* A {@link ControlFlowGraph} which provides a node comparator based on the
* pre-order traversal of the AST.
*/
private static class AstControlFlowGraph extends ControlFlowGraph<Node> {
private final Map<DiGraphNode<Node, Branch>, Integer> priorities;
/**
* Constructor.
* @param entry The entry node.
* @param priorities The map from nodes to position in the AST (to be
* filled by the {@link ControlFlowAnalysis#shouldTraverse}).
*/
private AstControlFlowGraph(Node entry,
Map<DiGraphNode<Node, Branch>, Integer> priorities,
boolean edgeAnnotations) {
super(entry,
true /* node annotations */, edgeAnnotations);
this.priorities = priorities;
}
@Override
/**
* Returns a node comparator based on the pre-order traversal of the AST.
* @param isForward x 'before' y in the pre-order traversal implies
* x 'less than' y (if true) and x 'greater than' y (if false).
*/
public Comparator<DiGraphNode<Node, Branch>> getOptionalNodeComparator(
boolean isForward) {
if (isForward) {
return new Comparator<DiGraphNode<Node, Branch>>() {
@Override
public int compare(
DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) {
return getPosition(n1) - getPosition(n2);
}
};
} else {
return new Comparator<DiGraphNode<Node, Branch>>() {
@Override
public int compare(
DiGraphNode<Node, Branch> n1, DiGraphNode<Node, Branch> n2) {
return getPosition(n2) - getPosition(n1);
}
};
}
}
/**
* Gets the pre-order traversal position of the given node.
* @return An arbitrary counter used for comparing positions.
*/
private int getPosition(DiGraphNode<Node, Branch> n) {
Integer priority = priorities.get(n);
Preconditions.checkNotNull(priority);
return priority;
}
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
this.lastGeneration = lastGeneration;
}
/**
* Tells the type system that {@code type} implements interface {@code
* InterfaceInstance}.
* {@code inter} must be an ObjectType for the instance of the interface as it
* could be a named type and not yet have the constructor.
*/
void registerTypeImplementingInterface(
FunctionType type, ObjectType interfaceInstance) {
interfaceToImplementors.put(interfaceInstance.getReferenceName(), type);
}
/**
* Returns a collection of types that directly implement {@code
* interfaceInstance}. Subtypes of implementing types are not guaranteed to
* be returned. {@code interfaceInstance} must be an ObjectType for the
* instance of the interface.
*/
public Collection<FunctionType> getDirectImplementors(
ObjectType interfaceInstance) {
return interfaceToImplementors.get(interfaceInstance.getReferenceName());
}
/**
* Records declared global type names. This makes resolution faster
* and more robust in the common case.
*
* @param name The name of the type to be recorded.
* @param t The actual type being associated with the name.
* @return True if this name is not already defined, false otherwise.
*/
public boolean declareType(String name, JSType t) {
if (namesToTypes.containsKey(name)) {
return false;
}
register(t, name);
return true;
}
/**
* Overrides a declared global type name. Throws an exception if this
* type name hasn't been declared yet.
*/
public void overwriteDeclaredType(String name, JSType t) {
Preconditions.checkState(namesToTypes.containsKey(name));
register(t, name);
}
/**
* Records a forward-declared type name. We will not emit errors if this
* type name never resolves to anything.
*/
public void forwardDeclareType(String name) {
forwardDeclaredTypes.add(name);
}
/**
* Whether this is a forward-declared type name.
*/
public boolean isForwardDeclaredType(String name) {
return forwardDeclaredTypes.contains(name);
}
/** Determines whether the given JS package exists. */
public boolean hasNamespace(String name) {
return namespaces.contains(name);
}
/**
* Looks up a type by name.
*
* @param jsTypeName The name string.
* @return the corresponding JSType object or {@code null} it cannot be found
*/
public JSType getType(String jsTypeName) {
// TODO(user): Push every local type name out of namesToTypes so that
// NamedType#resolve is correct.
if (jsTypeName.equals(templateTypeName)) {
return templateType;
}
return namesToTypes.get(jsTypeName);
}
public JSType getNativeType(JSTypeNative typeId) {
return nativeTypes[typeId.ordinal()];
}
public ObjectType getNativeObjectType(JSTypeNative typeId) {
return (ObjectType) getNativeType(typeId);
}
public FunctionType getNativeFunctionType(JSTypeNative typeId) {
return (FunctionType) getNativeType(typeId);
}
/**
* Looks up a type by name. To
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> allow for forward references to types, an
* unrecognized string has to be bound to a NamedType object that will be
* resolved later.
*
* @param scope A scope for doing type name resolution.
* @param jsTypeName The name string.
* @param sourceName The name of the source file where this reference appears.
* @param lineno The line number of the reference.
* @return a NamedType if the string argument is not one of the known types,
* otherwise the corresponding JSType object.
*/
public JSType getType(StaticScope<JSType> scope, String jsTypeName,
String sourceName, int lineno, int charno) {
JSType type = getType(jsTypeName);
if (type == null) {
// TODO(user): Each instance should support named type creation using
// interning.
NamedType namedType =
new NamedType(this, jsTypeName, sourceName, lineno, charno);
unresolvedNamedTypes.put(scope, namedType);
type = namedType;
}
return type;
}
/**
* Flushes out the current resolved and unresovled Named Types from
* the type registry. This is intended to be used ONLY before a
* compile is run.
*/
public void clearNamedTypes() {
resolvedNamedTypes.clear();
unresolvedNamedTypes.clear();
}
/**
* Resolve all the unresolved types in the given scope.
*/
public void resolveTypesInScope(StaticScope<JSType> scope) {
for (NamedType type : unresolvedNamedTypes.get(scope)) {
type.resolve(reporter, scope);
}
resolvedNamedTypes.putAll(scope, unresolvedNamedTypes.removeAll(scope));
if (scope != null && scope.getParentScope() == null) {
// By default, the global "this" type is just an anonymous object.
// If the user has defined a Window type, make the Window the
// implicit prototype of "this".
PrototypeObjectType globalThis = (PrototypeObjectType) getNativeType(
JSTypeNative.GLOBAL_THIS);
JSType windowType = getType("Window");
if (globalThis.isUnknownType()) {
ObjectType windowObjType = ObjectType.cast(windowType);
if (windowObjType != null) {
globalThis.setImplicitPrototype(windowObjType);
} else {
globalThis.setImplicitPrototype(
getNativeObjectType(JSTypeNative.OBJECT_TYPE));
}
}
}
}
/**
* Creates a type representing optional values of the given type.
* @return the union of the type and the void type
*/
public JSType createOptionalType(JSType type) {
if (type instanceof UnknownType || type.isAllType()) {
return type;
} else {
return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE));
}
}
/**
* Creates a type representing nullable values of the given type.
* @return the union of the type and the Null type
*/
public JSType createDefaultObjectUnion(JSType type) {
return shouldTolerateUndefinedValues()
? createOptionalNullableType(type)
: createNullableType(type);
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
* @param name the function's name
* @param source the node defining this function. Its type
* ({@link Node#getType()}) must be {@link Token#FUNCTION}.
*/
public FunctionType createInterfaceType(String name, Node source) {
return FunctionType.forInterface(this, name, source);
}
/**
* Creates a parameterized type.
*/
public ParameterizedType createParameterizedType(
ObjectType objectType, JSType parameterType) {
return new ParameterizedType(this, objectType, parameterType);
}
/**
* Creates a named type.
*/
@VisibleForTesting
public JSType createNamedType(String reference,
String sourceName, int lineno, int charno) {
return new NamedType(this, reference, sourceName, lineno, charno);
}
/**
* Identifies the name of a typedef or enum before we actually declare it.
*/
public void identifyNonNullableName(String name) {
Preconditions.checkNotNull(name);
nonNullableTypeNames.add(name);
}
/**
* Creates a JSType from the nodes representing a type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
public JSType createFromTypeNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
if (resolveMode == ResolveMode.LAZY_EXPRESSIONS) {
// If the type expression doesn't contain any names, just
// resolve it anyway.
boolean hasNames = hasTypeName(n);
if (hasNames) {
return new UnresolvedTypeExpression(this, n, sourceName);
}
}
return createFromTypeNodesInternal(n, sourceName, scope);
}
private boolean hasTypeName(Node n) {
if (n.getType() == Token.STRING) {
return true;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (hasTypeName(child)) {
return true;
}
}
return false;
}
/** @see #createFromTypeNodes(Node, String, StaticScope, boolean) */
private JSType createFromTypeNodesInternal(Node n, String sourceName,
StaticScope<JSType> scope) {
switch (n.getType()) {
case Token.LC: // Record type.
return createRecordTypeFromNodes(
n.getFirstChild(), sourceName, scope);
case Token.BANG: // Not nullable
return createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope)
.restrictByNotNullOrUndefined();
case Token.QMARK: // Nullable or unknown
Node firstChild = n.getFirstChild();
if (firstChild == null) {
return getNativeType(UNKNOWN_TYPE);
}
return createDefaultObjectUnion(
createFromTypeNodesInternal(
firstChild, sourceName, scope));
case Token.EQUALS: // Optional
return createOptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope));
case Token.ELLIPSIS: // Var args
return create
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>OptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope));
case Token.STAR: // The AllType
return getNativeType(ALL_TYPE);
case Token.LB: // Array type
// TODO(nicksantos): Enforce membership restrictions on the Array.
return getNativeType(ARRAY_TYPE);
case Token.PIPE: // Union type
UnionTypeBuilder builder = new UnionTypeBuilder(this);
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
builder.addAlternate(
createFromTypeNodesInternal(child, sourceName, scope));
}
return builder.build();
case Token.EMPTY: // When the return value of a function is not specified
return getNativeType(UNKNOWN_TYPE);
case Token.VOID: // Only allowed in the return value of a function.
return getNativeType(VOID_TYPE);
case Token.STRING:
JSType namedType = getType(scope, n.getString(), sourceName,
n.getLineno(), n.getCharno());
if (resolveMode != ResolveMode.LAZY_NAMES) {
namedType = namedType.resolveInternal(reporter, scope);
}
if ((namedType instanceof ObjectType) &&
!(nonNullableTypeNames.contains(n.getString()))) {
Node typeList = n.getFirstChild();
if (typeList != null &&
("Array".equals(n.getString()) ||
"Object".equals(n.getString()))) {
JSType parameterType =
createFromTypeNodesInternal(
typeList.getLastChild(), sourceName, scope);
namedType = new ParameterizedType(
this, (ObjectType) namedType, parameterType);
if (typeList.hasMoreThanOneChild()) {
JSType indexType =
createFromTypeNodesInternal(
typeList.getFirstChild(), sourceName, scope);
namedType = new IndexedType(
this, (ObjectType) namedType, indexType);
}
}
return createDefaultObjectUnion(namedType);
} else {
return namedType;
}
case Token.FUNCTION:
ObjectType thisType = null;
boolean isConstructor = false;
Node current = n.getFirstChild();
if (current.getType() == Token.THIS ||
current.getType() == Token.NEW) {
Node contextNode = current.getFirstChild();
thisType =
ObjectType.cast(
createFromTypeNodesInternal(
contextNode, sourceName, scope)
.restrictByNotNullOrUndefined());
if (thisType == null) {
reporter.warning(
ScriptRuntime.getMessage0(
current.getType() == Token.THIS ?
"msg.jsdoc.function.thisnotobject" :
"msg.jsdoc.function.newnotobject"),
sourceName,
contextNode.getLineno(), "", contextNode.getCharno());
}
isConstructor = current.getType() == Token.NEW;
current = current.getNext();
}
FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this);
if (current.getType() == Token.LP) {
Node args = current.getFirstChild();
for (Node arg = current.getFirstChild(); arg !=
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> null;
arg = arg.getNext()) {
if (arg.getType() == Token.ELLIPSIS) {
if (arg.getChildCount() == 0) {
paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE));
} else {
paramBuilder.addVarArgs(
createFromTypeNodesInternal(
arg.getFirstChild(), sourceName, scope));
}
} else {
JSType type = createFromTypeNodesInternal(
arg, sourceName, scope);
if (arg.getType() == Token.EQUALS) {
boolean addSuccess = paramBuilder.addOptionalParams(type);
if (!addSuccess) {
reporter.warning(
ScriptRuntime.getMessage0("msg.jsdoc.function.varargs"),
sourceName, arg.getLineno(), "", arg.getCharno());
}
} else {
paramBuilder.addRequiredParams(type);
}
}
}
current = current.getNext();
}
JSType returnType =
createFromTypeNodesInternal(current, sourceName, scope);
return new FunctionBuilder(this)
.withParams(paramBuilder)
.withReturnType(returnType)
.withTypeOfThis(thisType)
.setIsConstructor(isConstructor)
.build();
}
throw new IllegalStateException(
"Unexpected node in type expression: " + n.toString());
}
/**
* Creates a RecordType from the nodes representing said record type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
private JSType createRecordTypeFromNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
RecordTypeBuilder builder = new RecordTypeBuilder(this);
// For each of the fields in the record type.
for (Node fieldTypeNode = n.getFirstChild();
fieldTypeNode != null;
fieldTypeNode = fieldTypeNode.getNext()) {
// Get the property's name.
Node fieldNameNode = fieldTypeNode;
boolean hasType = false;
if (fieldTypeNode.getType() == Token.COLON) {
fieldNameNode = fieldTypeNode.getFirstChild();
hasType = true;
}
String fieldName = fieldNameNode.getString();
// TODO(user): Move this into the lexer/parser.
// Remove the string literal characters around a field name,
// if any.
if (fieldName.startsWith("'") || fieldName.startsWith("\"")) {
fieldName = fieldName.substring(1, fieldName.length() - 1);
}
// Get the property's type.
JSType fieldType = null;
if (hasType) {
// We have a declared type.
fieldType = createFromTypeNodesInternal(
fieldTypeNode.getLastChild(), sourceName, scope);
} else {
// Otherwise, the type is UNKNOWN.
fieldType = getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
// Add the property to the record.
if (builder.addProperty(fieldName, fieldType, fieldNameNode) == null) {
// Duplicate field name, warning and skip
reporter.warning(
"Duplicate record field " + fieldName,
sourceName,
n.getLineno(),
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
/**
* The minimum implementation of StaticSlot<JSType>.
*
* @author nicksantos@google.com (Nick Santos)
*/
public class SimpleSlot implements StaticSlot<JSType> {
final String name;
final JSType type;
final boolean inferred;
public SimpleSlot(String name, JSType type, boolean inferred) {
this.name = name;
this.type = type;
this.inferred = inferred;
}
public String getName() {
return name;
}
public JSType getType() {
return type;
}
public boolean isTypeInferred() {
return inferred;
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> comparator sorts the nodes in the direction of
* the flow.
* @return a comparator or null (in particular, if not overriden)
*/
public Comparator<DiGraphNode<N, Branch>> getOptionalNodeComparator(
boolean isForward) {
return null;
}
/**
* The edge object for the control flow graph.
*/
public static enum Branch {
/** Edge is taken if the condition is true. */
ON_TRUE,
/** Edge is taken if the condition is false. */
ON_FALSE,
/** Unconditional branch. */
UNCOND,
/** Exception related. */
ON_EX,
/** Possible folded-away template */
SYN_BLOCK;
public boolean isConditional() {
return this == ON_TRUE || this == ON_FALSE;
}
}
/**
* Abstract callback to visit a control flow graph node without going into
* subtrees of the node that is also represented by another control flow graph
* node.
*
* <p>For example, traversing an IF node as root will visit the two subtree
* pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and
* {@link ControlFlowGraph.Branch#ON_FALSE} edge.
*/
public abstract static class AbstractCfgNodeTraversalCallback implements
Callback {
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (parent == null) {
return true;
}
return !isEnteringNewCfgNode(n);
}
}
/**
* @return True if n should be represented by a new CFG node in the control
* flow graph.
*/
public static boolean isEnteringNewCfgNode(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.SCRIPT:
case Token.TRY:
case Token.FINALLY:
return true;
case Token.FUNCTION:
// A function node represents the start of a function where the name
// is bleed into the local scope and parameters has been assigned
// to the formal argument names. The node includes the name of the
// function and the LP list since we assume the whole set up process
// is atomic without change in control flow. The next change of
// control is going into the function's body represent by the second
// child.
return n != parent.getFirstChild().getNext();
case Token.WHILE:
case Token.DO:
case Token.IF:
// Theses control structure is represented by its node that holds the
// condition. Each of them is a branch node based on its condition.
return NodeUtil.getConditionExpression(parent) != n;
case Token.FOR:
// The FOR(;;) node differs from other control structure in that
// it has a initialization and a increment statement. Those
// two statements have its corresponding CFG nodes to represent them.
// The FOR node represents the condition check for each iteration.
// That way the following:
// for(var x = 0; x < 10; x++) { } has a graph that is isomorphic to
// var x = 0; while(x<10) { x++;
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> }
if (NodeUtil.isForIn(parent)) {
return n == parent.getLastChild();
} else {
return NodeUtil.getConditionExpression(parent) != n;
}
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.WITH:
return n != parent.getFirstChild();
default:
return false;
}
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> @return {@code this <: (String, string)}
*/
public final boolean isString() {
return this.isSubtype(
getNativeType(JSTypeNative.STRING_VALUE_OR_OBJECT_TYPE));
}
/**
* Tests whether the type is a number (value or Object).
* @return {@code this <: (Number, number)}
*/
public final boolean isNumber() {
return this.isSubtype(
getNativeType(JSTypeNative.NUMBER_VALUE_OR_OBJECT_TYPE));
}
public boolean isArrayType() {
return false;
}
public boolean isBooleanObjectType() {
return false;
}
public boolean isBooleanValueType() {
return false;
}
public boolean isRegexpType() {
return false;
}
public boolean isDateType() {
return false;
}
public boolean isNullType() {
return false;
}
public boolean isVoidType() {
return false;
}
public boolean isAllType() {
return false;
}
public boolean isUnknownType() {
return false;
}
public boolean isCheckedUnknownType() {
return false;
}
public boolean isUnionType() {
return false;
}
public boolean isFunctionType() {
return false;
}
public boolean isEnumElementType() {
return false;
}
public boolean isEnumType() {
return false;
}
boolean isNamedType() {
return false;
}
public boolean isRecordType() {
return false;
}
public boolean isTemplateType() {
return false;
}
/**
* Tests whether this type is an {@code Object}, or any subtype thereof.
* @return {@code this <: Object}
*/
public boolean isObject() {
return false;
}
/**
* Whether this type is a {@link FunctionType} that is a constructor or a
* named type that points to such a type.
*/
public boolean isConstructor() {
return false;
}
/**
* Whether this type is a nominal type (a named instance object or
* a named enum).
*/
public boolean isNominalType() {
return false;
}
/**
* Whether this type is an Instance object of some constructor.
*/
public boolean isInstanceType() {
return false;
}
/**
* Whether this type is a {@link FunctionType} that is an interface or a named
* type that points to such a type.
*/
public boolean isInterface() {
return false;
}
/**
* Whether this type is a {@link FunctionType} that is an ordinary function or
* a named type that points to such a type.
*/
public boolean isOrdinaryFunction() {
return false;
}
/**
* Checks if two types are equivalent.
*/
public boolean isEquivalentTo(JSType jsType) {
if (jsType instanceof ProxyObjectType) {
return jsType.isEquivalentTo(this);
}
// Relies on the fact that for the base {@link JSType}, only one
// instance of each sub-type will ever be created in a given registry, so
// there is no need to verify members
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> Returns whether this {@link JSDocInfo} contains a type for {@code @return}
* annotation.
*/
public boolean hasReturnType() {
return hasType(TYPEFIELD_RETURN);
}
private boolean hasType(int mask) {
return (bitset & MASK_TYPEFIELD) == mask;
}
/**
* Gets the type specified by the {@code @type} annotation.
*/
public JSTypeExpression getType() {
return getType(TYPEFIELD_TYPE);
}
/**
* Gets the return type specified by the {@code @return} annotation.
*/
public JSTypeExpression getReturnType() {
return getType(TYPEFIELD_RETURN);
}
/**
* Gets the enum parameter type specified by the {@code @enum} annotation.
*/
public JSTypeExpression getEnumParameterType() {
return getType(TYPEFIELD_ENUM);
}
/**
* Gets the typedef type specified by the {@code @type} annotation.
*/
public JSTypeExpression getTypedefType() {
return getType(TYPEFIELD_TYPEDEF);
}
private JSTypeExpression getType(int typefield) {
if ((MASK_TYPEFIELD & bitset) == typefield) {
return type;
} else {
return null;
}
}
/**
* Gets the type specified by the {@code @this} annotation.
*/
public JSTypeExpression getThisType() {
return thisType;
}
/**
* Sets the type specified by the {@code @this} annotation.
*/
void setThisType(JSTypeExpression type) {
this.thisType = type;
}
/**
* Returns whether this {@link JSDocInfo} contains a type for {@code @this}
* annotation.
*/
public boolean hasThisType() {
return thisType != null;
}
void setBaseType(JSTypeExpression type) {
lazyInitInfo();
info.baseType = type;
}
/**
* Gets the base type specified by the {@code @extends} annotation.
*/
public JSTypeExpression getBaseType() {
return (info == null) ? null : info.baseType;
}
/**
* Gets the description specified by the {@code @desc} annotation.
*/
public String getDescription() {
return (info == null) ? null : info.description;
}
void setDescription(String desc) {
lazyInitInfo();
info.description = desc;
}
/**
* Gets the meaning specified by the {@code @meaning} annotation.
*
* In localization systems, two messages with the same content but
* different "meanings" may be translated differently. By default, we
* use the name of the variable that the message is initialized to as
* the "meaning" of the message.
*
* But some code generators (like Closure Templates) inject their own
* meaning with the jsdoc {@code @meaning} annotation.
*/
public String getMeaning() {
return (info == null) ? null : info.meaning;
}
void setMeaning(String meaning) {
lazyInitInfo();
info.meaning = meaning;
}
/**
* Gets the name we're lending to in a {@code
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> (parent == null) {
// Find all the classes in the global scope.
newScope = createInitialScope(root);
GlobalScopeBuilder globalScopeBuilder = new GlobalScopeBuilder(newScope);
scopeBuilder = globalScopeBuilder;
NodeTraversal.traverse(compiler, root, scopeBuilder);
} else {
newScope = new Scope(parent, root);
LocalScopeBuilder localScopeBuilder = new LocalScopeBuilder(newScope);
scopeBuilder = localScopeBuilder;
localScopeBuilder.build();
}
scopeBuilder.resolveStubDeclarations();
scopeBuilder.resolveTypes();
// Gather the properties in each function that we found in the
// global scope, if that function has a @this type that we can
// build properties on.
for (Node functionNode : scopeBuilder.nonExternFunctions) {
JSType type = functionNode.getJSType();
if (type != null && type instanceof FunctionType) {
FunctionType fnType = (FunctionType) type;
ObjectType fnThisType = fnType.getTypeOfThis();
if (!fnThisType.isUnknownType()) {
NodeTraversal.traverse(compiler, functionNode.getLastChild(),
scopeBuilder.new CollectProperties(fnThisType));
}
}
}
if (parent == null) {
codingConvention.defineDelegateProxyPrototypeProperties(
typeRegistry, newScope, delegateProxyPrototypes);
}
return newScope;
}
/**
* Create the outermost scope. This scope contains native binding such as
* {@code Object}, {@code Date}, etc.
*/
@VisibleForTesting
Scope createInitialScope(Node root) {
NodeTraversal.traverse(
compiler, root, new DiscoverEnumsAndTypedefs(typeRegistry));
Scope s = new Scope(root, compiler);
declareNativeFunctionType(s, ARRAY_FUNCTION_TYPE);
declareNativeFunctionType(s, BOOLEAN_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, DATE_FUNCTION_TYPE);
declareNativeFunctionType(s, ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, EVAL_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, FUNCTION_FUNCTION_TYPE);
declareNativeFunctionType(s, NUMBER_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, RANGE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, REFERENCE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, REGEXP_FUNCTION_TYPE);
declareNativeFunctionType(s, STRING_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, SYNTAX_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, TYPE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, URI_ERROR_FUNCTION_TYPE);
declareNativeValueType(s, "undefined", VOID_TYPE);
// The typedef construct needs the any type, so that it can be assigned
// to anything. This is kind of a hack, and an artifact of the typedef
// syntax we've chosen.
declareNativeValueType(s, LEGACY_TYPEDEF, NO_TYPE);
// ActiveXObject is unqiuely special, because it can be used to construct
// any type (the type that it
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> creates is related to the arguments you
// pass to it).
declareNativeValueType(s, "ActiveXObject", NO_OBJECT_TYPE);
return s;
}
private void declareNativeFunctionType(Scope scope, JSTypeNative tId) {
FunctionType t = typeRegistry.getNativeFunctionType(tId);
declareNativeType(scope, t.getInstanceType().getReferenceName(), t);
declareNativeType(
scope, t.getPrototype().getReferenceName(), t.getPrototype());
}
private void declareNativeValueType(Scope scope, String name,
JSTypeNative tId) {
declareNativeType(scope, name, typeRegistry.getNativeType(tId));
}
private void declareNativeType(Scope scope, String name, JSType t) {
scope.declare(name, null, t, null, false);
}
private static class DiscoverEnumsAndTypedefs
extends AbstractShallowStatementCallback {
private final JSTypeRegistry registry;
DiscoverEnumsAndTypedefs(JSTypeRegistry registry) {
this.registry = registry;
}
@Override
public void visit(NodeTraversal t, Node node, Node parent) {
Node nameNode = null;
switch (node.getType()) {
case Token.VAR:
for (Node child = node.getFirstChild();
child != null; child = child.getNext()) {
identifyNameNode(
child, child.getFirstChild(),
NodeUtil.getInfoForNameNode(child));
}
break;
case Token.EXPR_RESULT:
Node firstChild = node.getFirstChild();
if (firstChild.getType() == Token.ASSIGN) {
identifyNameNode(
firstChild.getFirstChild(), firstChild.getLastChild(),
firstChild.getJSDocInfo());
} else {
identifyNameNode(
firstChild, null, firstChild.getJSDocInfo());
}
break;
}
}
private void identifyNameNode(
Node nameNode, Node valueNode, JSDocInfo info) {
if (nameNode.isQualifiedName()) {
if (info != null) {
if (info.hasEnumParameterType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
} else if (info.hasTypedefType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
}
}
if (valueNode != null &&
LEGACY_TYPEDEF.equals(valueNode.getQualifiedName())) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
}
}
}
}
/**
* Given a node, determines whether that node names a prototype
* property, and if so, returns the qualified name node representing
* the owner of that property. Otherwise, returns null.
*/
private static Node getPrototypePropertyOwner(Node n) {
if (n.getType() == Token.GETPROP) {
Node firstChild = n.getFirstChild();
if (firstChild.getType() == Token.GETPROP &&
firstChild.getLastChild().getString().equals("prototype")) {
Node maybeOwner = firstChild.getFirstChild();
if (maybeOwner.isQualifiedName()) {
return maybeOwner;
}
}
}
return null;
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> private JSType getNativeType(JSTypeNative nativeType) {
return typeRegistry.getNativeType(nativeType);
}
private abstract class AbstractScopeBuilder
implements NodeTraversal.Callback {
/**
* The scope that we're builidng.
*/
final Scope scope;
private final List<DeferredSetType> deferredSetTypes =
Lists.newArrayList();
/**
* Functions that we found in the global scope and not in externs.
*/
private final List<Node> nonExternFunctions = Lists.newArrayList();
/**
* Type-less stubs.
*
* If at the end of traversal, we still don't have types for these
* stubs, then we should declare UNKNOWN types.
*/
private final List<StubDeclaration> stubDeclarations =
Lists.newArrayList();
/**
* The current source file that we're in.
*/
private String sourceName = null;
private AbstractScopeBuilder(Scope scope) {
this.scope = scope;
}
void setDeferredType(Node node, JSType type) {
deferredSetTypes.add(new DeferredSetType(node, type));
}
void resolveTypes() {
// Resolve types and attach them to nodes.
for (DeferredSetType deferred : deferredSetTypes) {
deferred.resolve(scope);
}
// Resolve types and attach them to scope slots.
Iterator<Var> vars = scope.getVars();
while (vars.hasNext()) {
vars.next().resolveType(typeParsingErrorReporter);
}
// Tell the type registry that any remaining types
// are unknown.
typeRegistry.resolveTypesInScope(scope);
}
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (n.getType() == Token.FUNCTION ||
n.getType() == Token.SCRIPT) {
sourceName = NodeUtil.getSourceName(n);
}
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
boolean descend = parent == null || parent.getType() != Token.FUNCTION ||
n == parent.getFirstChild() || parent == scope.getRootNode();
if (descend) {
// Handle hoisted functions on pre-order traversal, so that they
// get hit before other things in the scope.
if (NodeUtil.isStatementParent(n)) {
for (Node child = n.getFirstChild();
child != null;
child = child.getNext()) {
if (NodeUtil.isHoistedFunctionDeclaration(child)) {
defineFunctionLiteral(child, n);
}
}
}
}
return descend;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
attachLiteralTypes(t, n);
switch (n.getType()) {
case Token.CALL:
checkForClassDefiningCalls(t, n, parent);
break;
case Token.FUNCTION:
if (t.getInput() == null || !t.getInput().isExtern()) {
nonExternFunctions.add(n);
}
// Hoisted functions are handled during pre-traversal.
if (!NodeUtil
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>.isHoistedFunctionDeclaration(n)) {
defineFunctionLiteral(n, parent);
}
break;
case Token.ASSIGN:
// Handle initialization of properties.
Node firstChild = n.getFirstChild();
if (firstChild.getType() == Token.GETPROP &&
firstChild.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(),
firstChild, n, firstChild.getNext());
}
break;
case Token.CATCH:
defineCatch(n, parent);
break;
case Token.VAR:
defineVar(n, parent);
break;
case Token.GETPROP:
// Handle stubbed properties.
if (parent.getType() == Token.EXPR_RESULT &&
n.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null);
}
break;
}
}
private void attachLiteralTypes(NodeTraversal t, Node n) {
switch (n.getType()) {
case Token.NULL:
n.setJSType(getNativeType(NULL_TYPE));
break;
case Token.VOID:
n.setJSType(getNativeType(VOID_TYPE));
break;
case Token.STRING:
// Defer keys to the Token.OBJECTLIT case
if (!NodeUtil.isObjectLitKey(n, n.getParent())) {
n.setJSType(getNativeType(STRING_TYPE));
}
break;
case Token.NUMBER:
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.TRUE:
case Token.FALSE:
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.REGEXP:
n.setJSType(getNativeType(REGEXP_TYPE));
break;
case Token.REF_SPECIAL:
n.setJSType(getNativeType(UNKNOWN_TYPE));
break;
case Token.OBJECTLIT:
defineObjectLiteral(t, n);
break;
// NOTE(nicksantos): If we ever support Array tuples,
// we will need to put ARRAYLIT here as well.
}
}
private void defineObjectLiteral(NodeTraversal t, Node objectLit) {
// Handle the @lends annotation.
JSType type = null;
JSDocInfo info = objectLit.getJSDocInfo();
if (info != null &&
info.getLendsName() != null) {
String lendsName = info.getLendsName();
Var lendsVar = scope.getVar(lendsName);
if (lendsVar == null) {
compiler.report(
JSError.make(sourceName, objectLit, UNKNOWN_LENDS, lendsName));
} else {
type = lendsVar.getType();
if (type == null) {
type = typeRegistry.getNativeType(UNKNOWN_TYPE);
}
if (!type.isSubtype(typeRegistry.getNativeType(OBJECT_TYPE))) {
compiler.report(
JSError.make(sourceName, objectLit, LENDS_ON_NON_OBJECT,
lendsName, type.toString()));
type
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> = null;
} else {
objectLit.setJSType(type);
}
}
}
info = getBestJSDocInfo(objectLit);
Node lValue = getBestLValue(objectLit);
String lValueName = getBestLValueName(lValue);
boolean createdEnumType = false;
if (info != null && info.hasEnumParameterType()) {
type = createEnumTypeFromNodes(objectLit, lValueName, info, lValue);
createdEnumType = true;
}
if (type == null) {
type = typeRegistry.createAnonymousObjectType();
}
setDeferredType(objectLit, type);
// If this is an enum, the properties were already taken care of above.
if (!createdEnumType) {
processObjectLitProperties(
t, objectLit, ObjectType.cast(objectLit.getJSType()));
}
}
/**
* Process an object literal and all the types on it.
* @param objLit The OBJECTLIT node.
* @param objLitType The type of the OBJECTLIT node. This might be a named
* type, because of the lends annotation.
*/
void processObjectLitProperties(
NodeTraversal t, Node objLit, ObjectType objLitType) {
for (Node keyNode = objLit.getFirstChild(); keyNode != null;
keyNode = keyNode.getNext()) {
Node value = keyNode.getFirstChild();
String memberName = NodeUtil.getObjectLitKeyName(keyNode);
JSDocInfo info = keyNode.getJSDocInfo();
JSType valueType = getDeclaredType(
t.getSourceName(), info, keyNode, value);
JSType keyType = NodeUtil.getObjectLitKeyTypeFromValueType(
keyNode, valueType);
if (keyType != null) {
// Try to declare this property in the current scope if it
// has an authoritative name.
String qualifiedName = getBestLValueName(keyNode);
if (qualifiedName != null) {
defineSlot(keyNode, objLit, qualifiedName, keyType, false);
} else {
setDeferredType(keyNode, keyType);
}
if (objLitType != null) {
// Declare this property on its object literal.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
objLitType.defineDeclaredProperty(
memberName, keyType, isExtern, keyNode);
}
}
}
}
/**
* Returns the type specified in a JSDoc annotation near a GETPROP or NAME.
*
* Extracts type information from either the {@code @type} tag or from
* the {@code @return} and {@code @param} tags.
*/
private JSType getDeclaredTypeInAnnotation(String sourceName,
Node node, JSDocInfo info) {
JSType jsType = null;
Node objNode =
node.getType() == Token.GETPROP ? node.getFirstChild() :
NodeUtil.isObjectLitKey(node, node.getParent()) ? node.getParent() :
null;
if (info != null) {
if (info.hasType()) {
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> jsType = info.getType().evaluate(scope, typeRegistry);
} else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) {
String fnName = node.getQualifiedName();
jsType = createFunctionTypeFromNodes(
null, fnName, info, node);
}
}
return jsType;
}
/**
* Asserts that it's ok to define this node's name.
* The node should have a source name and be of the specified type.
*/
void assertDefinitionNode(Node n, int type) {
Preconditions.checkState(sourceName != null);
Preconditions.checkState(n.getType() == type);
}
/**
* Defines a catch parameter.
*/
void defineCatch(Node n, Node parent) {
assertDefinitionNode(n, Token.CATCH);
Node catchName = n.getFirstChild();
defineSlot(catchName, n, null);
}
/**
* Defines a VAR initialization.
*/
void defineVar(Node n, Node parent) {
assertDefinitionNode(n, Token.VAR);
JSDocInfo info = n.getJSDocInfo();
if (n.hasMoreThanOneChild()) {
if (info != null) {
// multiple children
compiler.report(JSError.make(sourceName, n, MULTIPLE_VAR_DEF));
}
for (Node name : n.children()) {
defineName(name, n, parent, name.getJSDocInfo());
}
} else {
Node name = n.getFirstChild();
defineName(name, n, parent,
(info != null) ? info : name.getJSDocInfo());
}
}
/**
* Defines a function literal.
*/
void defineFunctionLiteral(Node n, Node parent) {
assertDefinitionNode(n, Token.FUNCTION);
// Determine the name and JSDocInfo and lvalue for the function.
// Any of these may be null.
Node lValue = getBestLValue(n);
JSDocInfo info = getBestJSDocInfo(n);
String functionName = getBestLValueName(lValue);
FunctionType functionType =
createFunctionTypeFromNodes(n, functionName, info, lValue);
// Assigning the function type to the function node
setDeferredType(n, functionType);
// Declare this symbol in the current scope iff it's a function
// declaration. Otherwise, the declaration will happen in other
// code paths.
if (NodeUtil.isFunctionDeclaration(n)) {
defineSlot(n.getFirstChild(), n, functionType);
}
}
/**
* Defines a variable based on the {@link Token#NAME} node passed.
* @param name The {@link Token#NAME} node.
* @param var The parent of the {@code name} node, which must be a
* {@link Token#VAR} node.
* @param parent {@code var}'s parent.
* @param info the {@link JSDocInfo} information relating to this
* {@code name} node.
*/
private void defineName(Node name, Node var, Node parent, JSDocInfo info) {
Node value =
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> name.getFirstChild();
// variable's type
JSType type = getDeclaredType(sourceName, info, name, value);
if (type == null) {
// The variable's type will be inferred.
CompilerInput input = compiler.getInput(sourceName);
Preconditions.checkNotNull(input, sourceName);
type = input.isExtern() ?
getNativeType(UNKNOWN_TYPE) : null;
}
defineSlot(name, var, type);
}
/**
* If a variable is assigned a function literal in the global scope,
* make that a declared type (even if there's no doc info).
* There's only one exception to this rule:
* if the return type is inferred, and we're in a local
* scope, we should assume the whole function is inferred.
*/
private boolean shouldUseFunctionLiteralType(
FunctionType type, JSDocInfo info, Node lValue) {
if (info != null) {
return true;
}
if (lValue != null &&
NodeUtil.isObjectLitKey(lValue, lValue.getParent())) {
return false;
}
return scope.isGlobal() || !type.isReturnTypeInferred();
}
/**
* Creates a new function type, based on the given nodes.
*
* This handles two cases that are semantically very different, but
* are not mutually exclusive:
* - A function literal that needs a type attached to it.
* - An assignment expression with function-type info in the jsdoc.
*
* All parameters are optional, and we will do the best we can to create
* a function type.
*
* This function will always create a function type, so only call it if
* you're sure that's what you want.
*
* @param rValue The function node.
* @param name the function's name
* @param info the {@link JSDocInfo} attached to the function definition
* @param lvalueNode The node where this function is being
* assigned. For example, {@code A.prototype.foo = ...} would be used to
* determine that this function is a method of A.prototype. May be
* null to indicate that this is not being assigned to a qualified name.
*/
private FunctionType createFunctionTypeFromNodes(
@Nullable Node rValue,
@Nullable String name,
@Nullable JSDocInfo info,
@Nullable Node lvalueNode) {
FunctionType functionType = null;
// Global ctor aliases should be registered with the type registry.
if (rValue != null && rValue.isQualifiedName() && scope.isGlobal()) {
Var var = scope.getVar(rValue.getQualifiedName());
if (var != null && var.getType() instanceof FunctionType) {
FunctionType aliasedType = (FunctionType) var.getType();
if ((aliasedType.isConstructor() || aliasedType.isInterface()) &&
!aliasedType.isNativeObjectType()) {
functionType = aliasedType;
if (name != null && scope.isGlobal()) {
typeRegistry.declareType(name, functionType.getInstanceType());
}
}
}
}
if (functionType ==
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> null) {
Node errorRoot = rValue == null ? lvalueNode : rValue;
boolean isFnLiteral =
rValue != null && rValue.getType() == Token.FUNCTION;
Node fnRoot = isFnLiteral ? rValue : null;
Node parametersNode = isFnLiteral ?
rValue.getFirstChild().getNext() : null;
Node fnBlock = isFnLiteral ? parametersNode.getNext() : null;
if (info != null && info.hasType()) {
JSType type = info.getType().evaluate(scope, typeRegistry);
// Known to be not null since we have the FUNCTION token there.
type = type.restrictByNotNullOrUndefined();
if (type.isFunctionType()) {
functionType = (FunctionType) type;
functionType.setJSDocInfo(info);
}
}
if (functionType == null) {
// Find the type of any overridden function.
FunctionType overriddenPropType = null;
if (lvalueNode != null &&
lvalueNode.getType() == Token.GETPROP &&
lvalueNode.isQualifiedName()) {
Var var = scope.getVar(
lvalueNode.getFirstChild().getQualifiedName());
if (var != null) {
ObjectType ownerType = ObjectType.cast(var.getType());
if (ownerType != null) {
String propName = lvalueNode.getLastChild().getString();
overriddenPropType =
findOverriddenFunction(ownerType, propName);
}
}
}
FunctionTypeBuilder builder =
new FunctionTypeBuilder(name, compiler, errorRoot, sourceName,
scope)
.setSourceNode(fnRoot)
.inferFromOverriddenFunction(overriddenPropType, parametersNode)
.inferTemplateTypeName(info)
.inferReturnType(info)
.inferInheritance(info);
// Infer the context type.
boolean searchedForThisType = false;
if (lvalueNode != null &&
lvalueNode.getType() == Token.GETPROP) {
Node objNode = lvalueNode.getFirstChild();
if (objNode.getType() == Token.GETPROP &&
objNode.getLastChild().getString().equals("prototype")) {
builder.inferThisType(info, objNode.getFirstChild());
searchedForThisType = true;
} else if (objNode.getType() == Token.THIS) {
builder.inferThisType(info, objNode.getJSType());
searchedForThisType = true;
}
}
if (!searchedForThisType) {
builder.inferThisType(info, (Node) null);
}
functionType = builder
.inferParameterTypes(parametersNode, info)
.inferReturnStatementsAsLastResort(fnBlock)
.buildAndRegister();
}
}
// all done
return functionType;
}
/**
* Find the function that's being overridden on this type, if any.
*/
private FunctionType findOverriddenFunction(
ObjectType ownerType, String propName) {
// First, check to see if the property is implemented
// on a superclass.
JSType propType = ownerType.getPropertyType(propName);
if (propType instanceof FunctionType) {
return (
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>FunctionType) propType;
} else {
// If it's not, then check to see if it's implemented
// on an implemented interface.
for (ObjectType iface :
ownerType.getCtorImplementedInterfaces()) {
propType = iface.getPropertyType(propName);
if (propType instanceof FunctionType) {
return (FunctionType) propType;
}
}
}
return null;
}
/**
* Creates a new enum type, based on the given nodes.
*
* This handles two cases that are semantically very different, but
* are not mutually exclusive:
* - An object literal that needs an enum type attached to it.
* - An assignment expression with an enum tag in the jsdoc.
*
* This function will always create an enum type, so only call it if
* you're sure that's what you want.
*
* @param rValue The node of the enum.
* @param name The enum's name
* @param info The {@link JSDocInfo} attached to the enum definition.
* @param lvalueNode The node where this function is being
* assigned.
*/
private EnumType createEnumTypeFromNodes(Node rValue, String name,
JSDocInfo info, Node lValueNode) {
Preconditions.checkNotNull(info);
Preconditions.checkState(info.hasEnumParameterType());
EnumType enumType = null;
if (rValue != null && rValue.isQualifiedName()) {
// Handle an aliased enum.
Var var = scope.getVar(rValue.getQualifiedName());
if (var != null && var.getType() instanceof EnumType) {
enumType = (EnumType) var.getType();
}
}
if (enumType == null) {
JSType elementsType =
info.getEnumParameterType().evaluate(scope, typeRegistry);
enumType = typeRegistry.createEnumType(name, elementsType);
if (rValue != null && rValue.getType() == Token.OBJECTLIT) {
// collect enum elements
Node key = rValue.getFirstChild();
while (key != null) {
String keyName = NodeUtil.getStringValue(key);
if (keyName == null) {
// GET and SET don't have a String value;
compiler.report(
JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName));
} else if (enumType.hasOwnProperty(keyName)) {
compiler.report(JSError.make(sourceName, key, ENUM_DUP, keyName));
} else if (!codingConvention.isValidEnumKey(keyName)) {
compiler.report(
JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName));
} else {
enumType.defineElement(keyName, key);
}
key = key.getNext();
}
}
}
if (name != null && scope.isGlobal()) {
typeRegistry.declareType(name, enumType.getElementsType());
}
return enumType;
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type or {@code null} if its type is inferred.
* @
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>param name the defining node. It must be a {@link Token#NAME}.
* @param parent the {@code name}'s parent.
* @param type the variable's type. It may be {@code null}, in which case
* the variable's type will be inferred.
*/
private void defineSlot(Node name, Node parent, JSType type) {
defineSlot(name, parent, type, type == null);
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is
* inferred.
*
* Slots may be any variable or any qualified name in the global scope.
*
* @param n the defining NAME or GETPROP node.
* @param parent the {@code n}'s parent.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
*/
void defineSlot(Node n, Node parent, JSType type, boolean inferred) {
Preconditions.checkArgument(inferred || type != null);
// Only allow declarations of NAMEs and qualfied names.
// Object literal keys will have to compute their names themselves.
if (n.getType() == Token.NAME) {
Preconditions.checkArgument(
parent.getType() == Token.FUNCTION ||
parent.getType() == Token.VAR ||
parent.getType() == Token.LP ||
parent.getType() == Token.CATCH);
} else {
Preconditions.checkArgument(
n.getType() == Token.GETPROP &&
(parent.getType() == Token.ASSIGN ||
parent.getType() == Token.EXPR_RESULT));
}
defineSlot(n, parent, n.getQualifiedName(), type, inferred);
}
/**
* Defines a symbol in the current scope.
*
* @param n the defining NAME or GETPROP or object literal key node.
* @param parent the {@code n}'s parent.
* @param variableName The name that this should be known by.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
* @param inferred Whether the type is inferred or declared.
*/
void defineSlot(Node n, Node parent, String variableName,
JSType type, boolean inferred) {
Preconditions.checkArgument(!variableName.isEmpty());
boolean isGlobalVar = n.getType() == Token.NAME && scope.isGlobal();
boolean shouldDeclareOnGlobalThis =
isGlobalVar &&
(parent.getType() == Token.VAR ||
parent.getType() == Token.FUNCTION);
// If n is a property, then we should really declare it in the
// scope where the root object appears. This helps out people
// who declare "global" names in an anonymous namespace.
Scope scopeToDeclareIn = scope;
if (n.getType() == Token.GETPROP && !scope.isGlobal() &&
isQnameRootedInGlobalScope(n)) {
Scope globalScope = scope.getGlobalScope();
// don't try to declare in the global scope if there's
// already a symbol there with this name
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>.
if (!globalScope.isDeclared(variableName, false)) {
scopeToDeclareIn = scope.getGlobalScope();
}
}
// declared in closest scope?
if (scopeToDeclareIn.isDeclared(variableName, false)) {
Var oldVar = scopeToDeclareIn.getVar(variableName);
validator.expectUndeclaredVariable(
sourceName, n, parent, oldVar, variableName, type);
} else {
if (!inferred) {
setDeferredType(n, type);
}
CompilerInput input = compiler.getInput(sourceName);
boolean isExtern = input.isExtern();
Var newVar =
scopeToDeclareIn.declare(variableName, n, type, input, inferred);
if (shouldDeclareOnGlobalThis) {
ObjectType globalThis =
typeRegistry.getNativeObjectType(GLOBAL_THIS);
if (inferred) {
globalThis.defineInferredProperty(variableName,
type == null ?
getNativeType(JSTypeNative.NO_TYPE) :
type,
isExtern, n);
} else {
globalThis.defineDeclaredProperty(variableName, type, isExtern, n);
}
}
if (type instanceof EnumType) {
Node initialValue = newVar.getInitialValue();
boolean isValidValue = initialValue != null &&
(initialValue.getType() == Token.OBJECTLIT ||
initialValue.isQualifiedName());
if (!isValidValue) {
compiler.report(JSError.make(sourceName, n, ENUM_INITIALIZER));
}
}
// We need to do some additional work for constructors and interfaces.
if (type instanceof FunctionType &&
// We don't want to look at empty function types.
!type.isEmptyType()) {
FunctionType fnType = (FunctionType) type;
if ((fnType.isConstructor() || fnType.isInterface()) &&
!fnType.equals(getNativeType(U2U_CONSTRUCTOR_TYPE))) {
// Declare var.prototype in the scope chain.
FunctionType superClassCtor = fnType.getSuperClassConstructor();
scopeToDeclareIn.declare(variableName + ".prototype", n,
fnType.getPrototype(), input,
/* declared iff there's an explicit supertype */
superClassCtor == null ||
superClassCtor.getInstanceType().equals(
getNativeType(OBJECT_TYPE)));
// Make sure the variable is initialized to something if
// it constructs itself.
if (newVar.getInitialValue() == null &&
!isExtern &&
// We want to make sure that when we declare a new instance
// type (with @constructor) that there's actually a ctor for it.
// This doesn't apply to structural constructors
// (like function(new:Array). Checking the constructed
// type against the variable name is a sufficient check for
// this.
variableName.equals(
fnType.getInstanceType().getReferenceName())) {
compiler.report(
JSError.make(sourceName, n,
fnType.isConstructor() ?
CTOR_INITIALIZER : IFACE_INITIALIZER,
variableName));
}
}
}
}
if (isGlobalVar && "Window".equals
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>(variableName)
&& type instanceof FunctionType
&& type.isConstructor()) {
FunctionType globalThisCtor =
typeRegistry.getNativeObjectType(GLOBAL_THIS).getConstructor();
globalThisCtor.getInstanceType().clearCachedValues();
globalThisCtor.getPrototype().clearCachedValues();
globalThisCtor
.setPrototypeBasedOn(((FunctionType) type).getInstanceType());
}
}
/**
* Check if the given node is a property of a name in the global scope.
*/
private boolean isQnameRootedInGlobalScope(Node n) {
Node root = NodeUtil.getRootOfQualifiedName(n);
if (root.getType() == Token.NAME) {
Var var = scope.getVar(root.getString());
if (var != null) {
return var.isGlobal();
}
}
return false;
}
/**
* Look for a type declaration on a property assignment
* (in an ASSIGN or an object literal key).
*
* @param info The doc info for this property.
* @param lValue The l-value node.
* @param rValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
private JSType getDeclaredType(String sourceName, JSDocInfo info,
Node lValue, @Nullable Node rValue) {
if (info != null && info.hasType()) {
return getDeclaredTypeInAnnotation(sourceName, lValue, info);
} else if (rValue != null && rValue.getType() == Token.FUNCTION &&
shouldUseFunctionLiteralType(
(FunctionType) rValue.getJSType(), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.getType() == Token.OBJECTLIT) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
if (rValue.getJSType() != null
&& !rValue.getJSType().isUnknownType()) {
return rValue.getJSType();
} else if (rValue.getType() == Token.OR) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.getType() == Token.NAME
&& lValue.getType() == Token.NAME
&& firstClause.getString().equals(lValue.getString());
if (namesMatch && secondClause.
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>getJSType() != null
&& !secondClause.getJSType().isUnknownType()) {
return secondClause.getJSType();
}
}
}
}
}
}
return getDeclaredTypeInAnnotation(sourceName, lValue, info);
}
/**
* Look for class-defining calls.
* Because JS has no 'native' syntax for defining classes,
* this is often very coding-convention dependent and business-logic heavy.
*/
private void checkForClassDefiningCalls(
NodeTraversal t, Node n, Node parent) {
SubclassRelationship relationship =
codingConvention.getClassesDefinedByCall(n);
if (relationship != null) {
ObjectType superClass = ObjectType.cast(
typeRegistry.getType(relationship.superclassName));
ObjectType subClass = ObjectType.cast(
typeRegistry.getType(relationship.subclassName));
if (superClass != null && subClass != null) {
FunctionType superCtor = superClass.getConstructor();
FunctionType subCtor = subClass.getConstructor();
if (relationship.type == SubclassType.INHERITS) {
validator.expectSuperType(t, n, superClass, subClass);
}
if (superCtor != null && subCtor != null) {
codingConvention.applySubclassRelationship(
superCtor, subCtor, relationship.type);
}
}
}
String singletonGetterClassName =
codingConvention.getSingletonGetterClassName(n);
if (singletonGetterClassName != null) {
ObjectType objectType = ObjectType.cast(
typeRegistry.getType(singletonGetterClassName));
if (objectType != null) {
FunctionType functionType = objectType.getConstructor();
if (functionType != null) {
FunctionType getterType =
typeRegistry.createFunctionType(objectType);
codingConvention.applySingletonGetter(functionType, getterType,
objectType);
}
}
}
DelegateRelationship delegateRelationship =
codingConvention.getDelegateRelationship(n);
if (delegateRelationship != null) {
applyDelegateRelationship(delegateRelationship);
}
ObjectLiteralCast objectLiteralCast =
codingConvention.getObjectLiteralCast(t, n);
if (objectLiteralCast != null) {
ObjectType type = ObjectType.cast(
typeRegistry.getType(objectLiteralCast.typeName));
if (type != null && type.getConstructor() != null) {
setDeferredType(objectLiteralCast.objectNode, type);
} else {
compiler.report(JSError.make(t.getSourceName(), n,
CONSTRUCTOR_EXPECTED));
}
}
}
/**
* Apply special properties that only apply to delegates.
*/
private void applyDelegateRelationship(
DelegateRelationship delegateRelationship) {
ObjectType delegatorObject = ObjectType.cast(
typeRegistry.getType(delegateRelationship.delegator));
ObjectType delegateBaseObject = ObjectType.cast(
typeRegistry.getType(delegateRelationship.delegateBase));
ObjectType delegateSuperObject = ObjectType.cast(
typeRegistry.getType(codingConvention.getDelegateSuperclassName()));
if (delegatorObject != null &&
delegateBaseObject != null &&
delegateSuperObject != null) {
FunctionType delegatorCtor = delegatorObject.getConstructor();
FunctionType delegateBaseCtor =
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>Type = getDeclaredType(t.getSourceName(), info, n, rhsValue);
if (valueType == null && rhsValue != null) {
// Determining type for #5
valueType = rhsValue.getJSType();
}
if (valueType == null) {
if (parent.getType() == Token.EXPR_RESULT) {
stubDeclarations.add(new StubDeclaration(
n,
t.getInput() != null && t.getInput().isExtern(),
ownerName));
}
return;
}
boolean inferred = true;
if (info != null) {
// Determining declaration for #1 + #3 + #4
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred) {
// Determining declaration for #2
inferred = !(rhsValue != null &&
rhsValue.getType() == Token.FUNCTION &&
!scope.isDeclared(qName, false));
}
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
ownerType.defineDeclaredProperty(propName, valueType, isExtern, n);
}
}
// If the property is already declared, the error will be
// caught when we try to declare it in the current scope.
defineSlot(n, parent, valueType, inferred);
} else if (rhsValue != null &&
rhsValue.getType() == Token.TRUE) {
// We declare these for delegate proxy method properties.
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType instanceof FunctionType) {
JSType ownerTypeOfThis = ((FunctionType) ownerType).getTypeOfThis();
String delegateName = codingConvention.getDelegateSuperclassName();
JSType delegateType = delegateName == null ?
null : typeRegistry.getType(delegateName);
if (delegateType != null &&
ownerTypeOfThis.isSubtype(delegateType)) {
defineSlot(n, parent, getNativeType(BOOLEAN_TYPE),
true);
}
}
}
}
/**
* Find the ObjectType associated with the given slot.
* @param slotName The name of the slot to find the type in.
* @return An object type, or null if this slot does not contain an object.
*/
private ObjectType getObjectSlot(String slotName) {
Var ownerVar = scope.getVar(slotName);
if (ownerVar != null) {
JSType ownerVarType = ownerVar.getType();
return ObjectType.cast(ownerVarType == null ?
null : ownerVarType.
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>restrictByNotNullOrUndefined());
}
return null;
}
/**
* Resolve any stub delcarations to unknown types if we could not
* find types for them during traversal.
*/
void resolveStubDeclarations() {
for (StubDeclaration stub : stubDeclarations) {
Node n = stub.node;
Node parent = n.getParent();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
String ownerName = stub.ownerName;
boolean isExtern = stub.isExtern;
if (scope.isDeclared(qName, false)) {
continue;
}
// If we see a stub property, make sure to register this property
// in the type registry.
ObjectType ownerType = getObjectSlot(ownerName);
ObjectType unknownType = typeRegistry.getNativeObjectType(UNKNOWN_TYPE);
defineSlot(n, parent, unknownType, true);
if (ownerType != null &&
(isExtern || ownerType.isFunctionPrototypeType())) {
// If this is a stub for a prototype, just declare it
// as an unknown type. These are seen often in externs.
ownerType.defineInferredProperty(
propName, unknownType, isExtern, n);
} else {
typeRegistry.registerPropertyOnType(
propName, ownerType == null ? unknownType : ownerType);
}
}
}
/**
* Collects all declared properties in a function, and
* resolves them relative to the global scope.
*/
private final class CollectProperties
extends AbstractShallowStatementCallback {
private final ObjectType thisType;
CollectProperties(ObjectType thisType) {
this.thisType = thisType;
}
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.getType() == Token.EXPR_RESULT) {
Node child = n.getFirstChild();
switch (child.getType()) {
case Token.ASSIGN:
maybeCollectMember(t, child.getFirstChild(), child,
child.getLastChild());
break;
case Token.GETPROP:
maybeCollectMember(t, child, child, null);
break;
}
}
}
private void maybeCollectMember(NodeTraversal t,
Node member, Node nodeWithJsDocInfo, @Nullable Node value) {
JSDocInfo info = nodeWithJsDocInfo.getJSDocInfo();
// Do nothing if there is no JSDoc type info, or
// if the node is not a member expression, or
// if the member expression is not of the form: this.someProperty.
if (info == null ||
member.getType() != Token.GETPROP ||
member.getFirstChild().getType() != Token.THIS) {
return;
}
member.getFirstChild().setJSType(thisType);
JSType jsType = getDeclaredType(t.getSourceName(), info, member, value);
Node name = member.getLastChild();
if (jsType != null &&
(name.getType() == Token.NAME || name.getType() == Token.STRING)) {
thisType.defineDeclaredProperty(
name.getString(),
jsType,
false /* functions with implementations are not in extern
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>s */,
member);
}
}
} // end CollectProperties
}
/**
* A stub declaration without any type information.
*/
private static final class StubDeclaration {
private final Node node;
private final boolean isExtern;
private final String ownerName;
private StubDeclaration(Node node, boolean isExtern, String ownerName) {
this.node = node;
this.isExtern = isExtern;
this.ownerName = ownerName;
}
}
/**
* A shallow traversal of the global scope to build up all classes,
* functions, and methods.
*/
private final class GlobalScopeBuilder extends AbstractScopeBuilder {
private GlobalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Visit a node in the global scope, and add anything it declares to the
* global symbol table.
*
* @param t The current traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
super.visit(t, n, parent);
switch (n.getType()) {
case Token.ASSIGN:
// Handle typedefs.
checkForOldStyleTypedef(t, n);
break;
case Token.VAR:
// Handle typedefs.
if (n.hasOneChild()) {
checkForOldStyleTypedef(t, n);
checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo());
}
break;
}
}
@Override
void maybeDeclareQualifiedName(
NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
checkForTypedef(t, n, info);
super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue);
}
/**
* Handle typedefs.
* @param t The current traversal.
* @param candidate A qualified name node.
* @param info JSDoc comments.
*/
private void checkForTypedef(
NodeTraversal t, Node candidate, JSDocInfo info) {
if (info == null || !info.hasTypedefType()) {
return;
}
String typedef = candidate.getQualifiedName();
if (typedef == null) {
return;
}
// TODO(nicksantos|user): This is a terrible, terrible hack
// to bail out on recusive typedefs. We'll eventually need
// to handle these properly.
typeRegistry.declareType(typedef, getNativeType(UNKNOWN_TYPE));
JSType realType = info.getTypedefType().evaluate(scope, typeRegistry);
if (realType == null) {
compiler.report(
JSError.make(
t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef));
}
typeRegistry.overwriteDeclaredType(typedef, realType);
if (candidate.getType() == Token.GETPROP) {
defineSlot(candidate, candidate.getParent(),
getNativeType(NO_TYPE), false);
}
}
/**
* Handle typedefs.
* @param t The current traversal.
* @param candidate An
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> ASSIGN or VAR node.
*/
// TODO(nicksantos): Kill this.
private void checkForOldStyleTypedef(NodeTraversal t, Node candidate) {
// old-style typedefs
String typedef = codingConvention.identifyTypeDefAssign(candidate);
if (typedef != null) {
// TODO(nicksantos|user): This is a terrible, terrible hack
// to bail out on recusive typedefs. We'll eventually need
// to handle these properly.
typeRegistry.declareType(typedef, getNativeType(UNKNOWN_TYPE));
JSDocInfo info = candidate.getJSDocInfo();
JSType realType = null;
if (info != null && info.getType() != null) {
realType = info.getType().evaluate(scope, typeRegistry);
}
if (realType == null) {
compiler.report(
JSError.make(
t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef));
}
typeRegistry.overwriteDeclaredType(typedef, realType);
// Duplicate typedefs get handled when we try to register
// this typedef in the scope.
}
}
} // end GlobalScopeBuilder
/**
* A shallow traversal of a local scope to find all arguments and
* local variables.
*/
private final class LocalScopeBuilder extends AbstractScopeBuilder {
/**
* @param scope The scope that we're builidng.
*/
private LocalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Traverse the scope root and build it.
*/
void build() {
NodeTraversal.traverse(compiler, scope.getRootNode(), this);
}
/**
* Visit a node in a local scope, and add any local variables or catch
* parameters into the local symbol table.
*
* @param t The node traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n == scope.getRootNode()) return;
if (n.getType() == Token.LP && parent == scope.getRootNode()) {
handleFunctionInputs(parent);
return;
}
super.visit(t, n, parent);
}
/** Handle bleeding functions and function parameters. */
private void handleFunctionInputs(Node fnNode) {
// Handle bleeding functions.
Node fnNameNode = fnNode.getFirstChild();
String fnName = fnNameNode.getString();
if (!fnName.isEmpty()) {
Scope.Var fnVar = scope.getVar(fnName);
if (fnVar == null ||
// Make sure we're not touching a native function. Native
// functions aren't bleeding, but may not have a declaration
// node.
(fnVar.getNameNode() != null &&
// Make sure that the function is actually bleeding by checking
// if has already been declared.
fnVar.getInitialValue() != fnNode)) {
defineSlot(fnNameNode, fnNode, fnNode.getJSType(), false);
}
}
declareArguments(fnNode);
}
/**
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> * Declares all of a function's arguments.
*/
private void declareArguments(Node functionNode) {
Node astParameters = functionNode.getFirstChild().getNext();
Node body = astParameters.getNext();
FunctionType functionType = (FunctionType) functionNode.getJSType();
if (functionType != null) {
Node jsDocParameters = functionType.getParametersNode();
if (jsDocParameters != null) {
Node jsDocParameter = jsDocParameters.getFirstChild();
for (Node astParameter : astParameters.children()) {
if (jsDocParameter != null) {
defineSlot(astParameter, functionNode,
jsDocParameter.getJSType(), false);
jsDocParameter = jsDocParameter.getNext();
} else {
defineSlot(astParameter, functionNode, null, true);
}
}
}
}
} // end declareArguments
} // end LocalScopeBuilder
/** Find the best JSDoc for the given node. */
static JSDocInfo getBestJSDocInfo(Node n) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
Node parent = n.getParent();
int parentType = parent.getType();
if (parentType == Token.NAME) {
info = parent.getJSDocInfo();
if (info == null && parent.getParent().hasOneChild()) {
info = parent.getParent().getJSDocInfo();
}
} else if (parentType == Token.ASSIGN) {
info = parent.getJSDocInfo();
} else if (NodeUtil.isObjectLitKey(parent, parent.getParent())) {
info = parent.getJSDocInfo();
}
}
return info;
}
/** Find the l-value that the given r-value is being assigned to. */
private static Node getBestLValue(Node n) {
Node parent = n.getParent();
int parentType = parent.getType();
boolean isFunctionDeclaration = NodeUtil.isFunctionDeclaration(n);
if (isFunctionDeclaration) {
return n.getFirstChild();
} else if (parentType == Token.NAME) {
return parent;
} else if (parentType == Token.ASSIGN) {
return parent.getFirstChild();
} else if (NodeUtil.isObjectLitKey(parent, parent.getParent())) {
return parent;
}
return null;
}
/** Get the name of the given l-value node. */
private static String getBestLValueName(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (NodeUtil.isObjectLitKey(lValue, lValue.getParent())) {
Node owner = getBestLValue(lValue.getParent());
if (owner != null) {
String ownerName = getBestLValueName(owner);
if (ownerName != null) {
return ownerName + "." + NodeUtil.getObjectLitKeyName(lValue);
}
}
return null;
}
return lValue.getQualifiedName();
}
}
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Iterables;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.jscomp.graph.GraphvizGraph;
import com.google.javascript.jscomp.graph.LinkedDirectedGraph;
import com.google.javascript.rhino.Node;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Pass factories and meta-data for native Compiler passes.
*
* @author nicksantos@google.com (Nick Santos)
*/
public abstract class PassConfig {
// Used by subclasses in this package.
final CompilerOptions options;
/**
* A memoized version of scopeCreator. It must be memoized so that
* we can make two separate passes over the AST, one for inferring types
* and one for checking types.
*/
private MemoizedScopeCreator typedScopeCreator;
/** The global typed scope. */
Scope topScope = null;
public PassConfig(CompilerOptions options) {
this.options = options;
}
/**
* Regenerates the top scope.
*/
void regenerateGlobalTypedScope(AbstractCompiler compiler, Node root) {
MemoizedScopeCreator oldScopeCreator = typedScopeCreator;
typedScopeCreator =
new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = typedScopeCreator.createScope(root, null);
if (oldScopeCreator != null) {
Scope oldTopScope = oldScopeCreator.getScopeIfMemoized(root);
if (oldTopScope != null) {
// For each variable declared with the VAR keyword, we want to grab
// its old inferred type.
//
// This is purely a heuristic. There are probably better ones we
// can use to increase the accuracy (like checking if a variable
// has been modified in the current script).
Iterator<Var> varIt =
topScope.getDeclarativelyUnboundVarsWithoutTypes();
while (varIt.hasNext()) {
Var newVar = varIt.next();
Var oldVar = oldTopScope.getVar(newVar.getName());
if (oldVar != null) {
newVar.setType(oldVar.getType());
}
}
}
}
}
/**
* Gets the scope creator for typed scopes.
*/
ScopeCreator get
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>Type call;
/**
* The {@code prototype} property. This field is lazily initialized by
* {@code #getPrototype()}. The most important reason for lazily
* initializing this field is that there are cycles in the native types
* graph, so some prototypes must temporarily be {@code null} during
* the construction of the graph.
*/
private FunctionPrototypeType prototype;
/**
* Whether a function is a constructor, an interface, or just an ordinary
* function.
*/
private final Kind kind;
/**
* The type of {@code this} in the scope of this function.
*/
private ObjectType typeOfThis;
/**
* The function node which this type represents. It may be {@code null}.
*/
private Node source;
/**
* The interfaces directly implemented by this function (for constructors)
* It is only relevant for constructors. May not be {@code null}.
*/
private List<ObjectType> implementedInterfaces = ImmutableList.of();
/**
* The interfaces directly extendeded by this function (for interfaces)
* It is only relevant for constructors. May not be {@code null}.
*/
private List<ObjectType> extendedInterfaces = ImmutableList.of();
/**
* The types which are subtypes of this function. It is only relevant for
* constructors and may be {@code null}.
*/
private List<FunctionType> subTypes;
/**
* The template type name. May be {@code null}.
*/
private String templateTypeName;
/** Creates an instance for a function that might be a constructor. */
FunctionType(JSTypeRegistry registry, String name, Node source,
ArrowType arrowType, ObjectType typeOfThis,
String templateTypeName, boolean isConstructor, boolean nativeType) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
nativeType);
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkNotNull(arrowType);
this.source = source;
this.kind = isConstructor ? Kind.CONSTRUCTOR : Kind.ORDINARY;
if (isConstructor) {
this.typeOfThis = typeOfThis != null ?
typeOfThis : new InstanceObjectType(registry, this, nativeType);
} else {
this.typeOfThis = typeOfThis != null ?
typeOfThis :
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE);
}
this.call = arrowType;
this.templateTypeName = templateTypeName;
}
/** Creates an instance for a function that is an interface. */
private FunctionType(JSTypeRegistry registry, String name, Node source) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE));
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkArgument(name != null);
this.source = source;
this.call = new ArrowType(registry, new Node(Token.LP), null);
this.kind = Kind.INTERFACE;
this.typeOfThis = new InstanceObjectType(registry, this);
}
/** Creates an instance for a function that is an interface. */
static FunctionType forInterface(
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>
JSTypeRegistry registry, String name, Node source) {
return new FunctionType(registry, name, source);
}
@Override
public boolean isInstanceType() {
// The universal constructor is its own instance, bizarrely.
return isEquivalentTo(registry.getNativeType(U2U_CONSTRUCTOR_TYPE));
}
@Override
public boolean isConstructor() {
return kind == Kind.CONSTRUCTOR;
}
@Override
public boolean isInterface() {
return kind == Kind.INTERFACE;
}
@Override
public boolean isOrdinaryFunction() {
return kind == Kind.ORDINARY;
}
@Override
public boolean isFunctionType() {
return true;
}
@Override
public boolean canBeCalled() {
return true;
}
public boolean hasImplementedInterfaces() {
if (!implementedInterfaces.isEmpty()){
return true;
}
FunctionType superCtor = isConstructor() ?
getSuperClassConstructor() : null;
if (superCtor != null) {
return superCtor.hasImplementedInterfaces();
}
return false;
}
public Iterable<Node> getParameters() {
Node n = getParametersNode();
if (n != null) {
return n.children();
} else {
return Collections.emptySet();
}
}
/** Gets an LP node that contains all params. May be null. */
public Node getParametersNode() {
return call.parameters;
}
/** Gets the minimum number of arguments that this function requires. */
public int getMinArguments() {
// NOTE(nicksantos): There are some native functions that have optional
// parameters before required parameters. This algorithm finds the position
// of the last required parameter.
int i = 0;
int min = 0;
for (Node n : getParameters()) {
i++;
if (!n.isOptionalArg() && !n.isVarArgs()) {
min = i;
}
}
return min;
}
/**
* Gets the maximum number of arguments that this function requires,
* or Integer.MAX_VALUE if this is a variable argument function.
*/
public int getMaxArguments() {
Node params = getParametersNode();
if (params != null) {
Node lastParam = params.getLastChild();
if (lastParam == null || !lastParam.isVarArgs()) {
return params.getChildCount();
}
}
return Integer.MAX_VALUE;
}
public JSType getReturnType() {
return call.returnType;
}
public boolean isReturnTypeInferred() {
return call.returnTypeInferred;
}
/** Gets the internal arrow type. For use by subclasses only. */
ArrowType getInternalArrowType() {
return call;
}
/**
* Gets the {@code prototype} property of this function type. This is
* equivalent to {@code (ObjectType) getPropertyType("prototype")}.
*/
public FunctionPrototypeType getPrototype() {
// lazy initialization of the prototype field
if (prototype == null) {
setPrototype(new FunctionPrototypeType(registry, this, null));
}
return prototype;
}
/**
* Sets the prototype, creating the prototype object from the given
*
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS> in a linked hash set, so that the compile job is
// deterministic.
Set<ObjectType> extendedInterfaces = Sets.newLinkedHashSet();
for (ObjectType interfaceType : getExtendedInterfaces()) {
addRelatedExtendedInterfaces(interfaceType, extendedInterfaces);
}
return extendedInterfaces;
}
private void addRelatedExtendedInterfaces(ObjectType instance,
Set<ObjectType> set) {
FunctionType constructor = instance.getConstructor();
if (constructor != null) {
set.add(instance);
for (ObjectType interfaceType : constructor.getExtendedInterfaces()) {
addRelatedExtendedInterfaces(interfaceType, set);
}
}
}
/** Returns interfaces directly extended by an interface */
public Iterable<ObjectType> getExtendedInterfaces() {
return extendedInterfaces;
}
/** Returns the number of interfaces directly extended by an interface */
public int getExtendedInterfacesCount() {
return extendedInterfaces.size();
}
public void setExtendedInterfaces(List<ObjectType> extendedInterfaces)
throws UnsupportedOperationException {
if (isInterface()) {
this.extendedInterfaces = ImmutableList.copyOf(extendedInterfaces);
} else {
throw new UnsupportedOperationException();
}
}
@Override
public boolean hasProperty(String name) {
return super.hasProperty(name) || "prototype".equals(name);
}
@Override
public boolean hasOwnProperty(String name) {
return super.hasOwnProperty(name) || "prototype".equals(name);
}
@Override
public JSType getPropertyType(String name) {
if ("prototype".equals(name)) {
return getPrototype();
} else {
if (!hasOwnProperty(name)) {
if ("call".equals(name)) {
// Define the "call" function lazily.
Node params = getParametersNode();
if (params == null) {
// If there's no params array, don't do any type-checking
// in this CALL function.
defineDeclaredProperty(name,
new FunctionBuilder(registry)
.withReturnType(getReturnType())
.build(),
false, source);
} else {
params = params.cloneTree();
Node thisTypeNode = Node.newString(Token.NAME, "thisType");
thisTypeNode.setJSType(
registry.createOptionalNullableType(getTypeOfThis()));
params.addChildToFront(thisTypeNode);
thisTypeNode.setOptionalArg(true);
defineDeclaredProperty(name,
new FunctionBuilder(registry)
.withParamsNode(params)
.withReturnType(getReturnType())
.build(),
false, source);
}
} else if ("apply".equals(name)) {
// Define the "apply" function lazily.
FunctionParamBuilder builder = new FunctionParamBuilder(registry);
// Ecma-262 says that apply's second argument must be an Array
// or an arguments object. We don't model the arguments object,
// so let's just be forgiving for now.
// TODO(nicksantos): Model the Arguments object.
builder.addOptionalParams(
registry.createNullableType(getTypeOfThis()),
registry.createNullableType(
registry.getNativeType(JSTypeNative.OBJECT_TYPE)));
defineDeclaredProperty(name,
new FunctionBuilder(
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>registry)
.withParams(builder)
.withReturnType(getReturnType())
.build(),
false, source);
}
}
return super.getPropertyType(name);
}
}
@Override
boolean defineProperty(String name, JSType type,
boolean inferred, boolean inExterns, Node propertyNode) {
if ("prototype".equals(name)) {
ObjectType objType = type.toObjectType();
if (objType != null) {
if (objType.isEquivalentTo(prototype)) {
return true;
}
return setPrototype(
new FunctionPrototypeType(
registry, this, objType, isNativeObjectType()));
} else {
return false;
}
}
return super.defineProperty(name, type, inferred, inExterns, propertyNode);
}
@Override
public boolean isPropertyTypeInferred(String property) {
return "prototype".equals(property) ||
super.isPropertyTypeInferred(property);
}
@Override
public JSType getLeastSupertype(JSType that) {
return supAndInfHelper(that, true);
}
@Override
public JSType getGreatestSubtype(JSType that) {
return supAndInfHelper(that, false);
}
/**
* Computes the supremum or infimum of functions with other types.
* Because sup() and inf() share a lot of logic for functions, we use
* a single helper.
* @param leastSuper If true, compute the supremum of {@code this} with
* {@code that}. Otherwise compute the infimum.
* @return The least supertype or greatest subtype.
*/
private JSType supAndInfHelper(JSType that, boolean leastSuper) {
// NOTE(nicksantos): When we remove the unknown type, the function types
// form a lattice with the universal constructor at the top of the lattice,
// and the LEAST_FUNCTION_TYPE type at the bottom of the lattice.
//
// When we introduce the unknown type, it's much more difficult to make
// heads or tails of the partial ordering of types, because there's no
// clear hierarchy between the different components (parameter types and
// return types) in the ArrowType.
//
// Rather than make the situation more complicated by introducing new
// types (like unions of functions), we just fallback on the simpler
// approach of getting things right at the top and the bottom of the
// lattice.
if (isFunctionType() && that.isFunctionType()) {
if (isEquivalentTo(that)) {
return this;
}
FunctionType other = null;
if (that instanceof FunctionType) {
other = (FunctionType) that;
}
// If these are ordinary functions, then merge them.
// Don't do this if any of the params/return
// values are unknown, because then there will be cycles in
// their local lattice and they will merge in weird ways.
if (other != null &&
isOrdinaryFunction() && that.isOrdinaryFunction() &&
!this.call.hasUnknownParamsOrReturn() &&
!other.call.hasUnknownParamsOrReturn()) {
// Check for the degenerate case, but double
Closure, 69
<FILEB>
<CHANGES>
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.getType() == Token.GETELEM ||
child.getType() == Token.GETPROP)) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
<CHANGEE>
<FILEE>
<FILEB>
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if(functionJSDocInfo != null) {
String sourceName = functionJSDocInfo.getSourceName();
CompilerInput functionSource = compiler.getInput(sourceName);
isExtern = functionSource.isExtern();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explcit 'this' types must be called in a GETPROP
// or GETELEM.
<CHANGES>
<CHANGEE>
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or warn
// about the non-portability of the call or both.
}
/**
* Visits the parameters of a CALL or a NEW node.
*/
<FILEE>
<SCANS>this:SubFoo)
// is not a subtype of function(this:Foo), our override check treats
// this as an error. It also screws up out standard method
// for aliasing constructors. Let's punt on all this for now.
// TODO(nicksantos): fix this.
FunctionType other = (FunctionType) that;
boolean treatThisTypesAsCovariant =
// If either one of these is a ctor, skip 'this' checking.
this.isConstructor() || other.isConstructor() ||
// An interface 'this'-type is non-restrictive.
// In practical terms, if C implements I, and I has a method m,
// then any m doesn't necessarily have to C#m's 'this'
// type doesn't need to match I.
(other.typeOfThis.getConstructor() != null &&
other.typeOfThis.getConstructor().isInterface()) ||
// If one of the 'this' types is covariant of the other,
// then we'll treat them as covariant (see comment above).
other.typeOfThis.isSubtype(this.typeOfThis) ||
this.typeOfThis.isSubtype(other.typeOfThis);
return treatThisTypesAsCovariant && this.call.isSubtype(other.call);
}
return getNativeType(JSTypeNative.FUNCTION_PROTOTYPE).isSubtype(that);
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseFunctionType(this);
}
/**
* Gets the type of instance of this function.
* @throws IllegalStateException if this function is not a constructor
* (see {@link #isConstructor()}).
*/
public ObjectType getInstanceType() {
Preconditions.checkState(hasInstanceType());
return typeOfThis;
}
/**
* Sets the instance type. This should only be used for special
* native types.
*/
void setInstanceType(ObjectType instanceType) {
typeOfThis = instanceType;
}
/**
* Returns whether this function type has an instance type.
*/
public boolean hasInstanceType() {
return isConstructor() || isInterface();
}
/**
* Gets the type of {@code this} in this function.
*/
public ObjectType getTypeOfThis() {
return typeOfThis.isNoObjectType() ?
registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE) : typeOfThis;
}
/**
* Gets the source node or null if this is an unknown function.
*/
public Node getSource() {
return source;
}
/**
* Sets the source node.
*/
public void setSource(Node source) {
this.source = source;
}
/** Adds a type to the list of subtypes for this type. */
private void addSubType(FunctionType subType) {
if (subTypes == null) {
subTypes = Lists.newArrayList();
}
subTypes.add(subType);
}
@Override
public void clearCachedValues() {
super.clearCachedValues();
if (subTypes != null) {
for (FunctionType subType : subTypes) {
subType.clearCachedValues();
}